长时间运行的异步文件副本会阻止浏览器请求



Express.js提供Remix应用程序。服务器端代码在启动时设置了几个计时器,每隔一段时间就会执行各种后台工作,其中一个计时器会检查远程Jenkins构建是否完成。如果是这样,它会将几个大型PDF从一个网络路径复制到另一个网络通路(都在GSA上(。

一个函数创建一个链接glob+copyFile promise的数组:

import { copyFile } from 'node:fs/promises';
import { promisify } from "util";
import glob from "glob";
...
async function getFiles() {
let result: Promise<void>[] = [];
let globPromise = promisify(glob);
for (let wildcard of wildcards) { // lots of file wildcards here
result.push(globPromise(wildcard).then(
(files: string[]) => {
if (files.length < 1) {
// do error stuff
} else {
for (let srcFile of files) {
let tgtFile = tgtDir + basename(srcFile);
return copyFile(srcFile, tgtFile);
}
}
},
(reason: any) => {
// do error stuff
}));
}
return result;
}

另一个异步函数获取该数组并对其执行Promise.allSettled:

copyPromises = await getFiles();
console.log("CALLING ALLSETTLED.THEN()...");
return Promise.allSettled(copyPromises).then(
(results) => {
console.log("ALLSETTLED COMPLETE...");

在";呼叫";以及";完整";消息,可能需要几分钟的时间,服务器不再响应浏览器请求,这会超时。

然而,在这段时间里,仍然可以在服务器控制台日志中看到我的其他活动后端计时器正在运行并完成得很好(出于测试目的,我每5秒运行一次,当这些文件副本在爬行时,它运行得非常平稳(。

因此,它并没有阻止整个服务器,它似乎只是阻止浏览器请求得到处理。一旦";完整";日志中弹出消息,浏览器请求再次正常送达。

Express的启动脚本基本上只是为Remix:做这件事

const { createRequestHandler } = require("@remix-run/express");
...
app.all(
"*",
createRequestHandler({
build: require(BUILD_DIR),
mode: process.env.NODE_ENV,
})
);

这里发生了什么,我该如何解决?

很明显,不会有进一步的讨论,我还没有确定异步I/O功能阻止服务器响应的原因,所以我将继续从评论中发布一个基本上是Konrad Linkowski的解决方案的答案:使用操作系统而不是使用copyFile((进行复制。它可以归结为代替getFiles中的glob+copyFile调用:

const exec = util.promisify(require('node:child_process').exec);
...
async function getFiles() {
...
result.push( exec("copy /y " + wildcard + " " + tgtDir) );
...
}

这不会表现出任何请求破坏行为;在整个复制过程中(很多分钟(,浏览器请求都会立即得到处理。

这是一个特定于操作系统的解决方案,因此是不可移植的,但在我们的情况下,这很好,因为在未来的许多年里,我们可能会为这个应用程序使用Windows服务器。当然,如果需要,运行时操作系统检测可以用于使命令在其他操作系统上运行。

我想这是由于节点的libuv使用了一个具有同步访问权限的线程池来进行文件系统操作,并且池大小只有4。看见https://kariera.future-processing.pl/blog/on-problems-with-threads-in-node-js/为了演示这个问题,或者Nodejs-当线程池大小为4时,可以同时运行的最大线程是多少?以解释在网络密集型应用程序中这通常不是问题。

因此,如果您有一个文件系统访问量很大的应用程序,请尝试通过设置UV_THREADPOOL_SIZE环境变量来增加线程池。

最新更新