Node/Typescript: 如何在进程 'exit' 事件中关闭 writestream - red/blue 功能问题/异步感染

Node/Typescript: How to close a writestream in the process 'exit' event - red/blue function problem / async infection

my current FOSS Discord bot project 中,我有这个 log.ts 文件来处理机器人的日志记录。

它创建多个 fs.WriteStream 对象,写入每个日志文件。当 await log('CLOSE_STREAMS') 被调用到每个 WriteStream 上的 运行 WriteStream#close() 函数时,代码中有一段,返回一个承诺。这在 process.on('exit') 处理程序中用于在我们关闭之前保存日志文件。

这里的问题是 'exit' 事件处理程序无法将任何额外的工作安排到事件队列中。

我如何处理 CLOSE_STREAMS 调用 运行 我期望的退出处理程序?

函数实现,简化

log.ts log('CLOSE_STREAMS')

// Main
export default function log(mode: 'CLOSE_STREAMS'): Promise<void>;
export default function log(mode: 'v' | 'i' | 'w' | 'e', message: any, _bypassStackPrint?: boolean): void;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export default function log(mode: 'v' | 'i' | 'w' | 'e' | 'CLOSE_STREAMS', message?: any, _bypassStackPrint = false): void | Promise<void> {
    if (mode === 'CLOSE_STREAMS')
        // Close all of the file streams
        return new Promise((resolve) => {
            errStr.end(() => {
                warnStr.end(() => {
                    allStr.end(() => {
                        resolve();
                    });
                });
            });
        });
    else {

index.ts

这是日志在未捕获异常中被杀死的方式;这就是我想为退出事件做的。

// If we get an uncaught exception, close ASAP.
process.on('uncaughtException', async (error) => {
    log('e', 'Killing client...', true);
    client.destroy();
    log('e', 'Client killed.', true);
    log('e', 'Closing databases...', true);
    client.closeDatabases();
    log('e', 'Closed databases.', true);
    log('e', 'An uncaught exception occured!', true);
    log('e', `Error thrown was:`, true);
    error.stack?.split('\n').forEach((item) => {
        log('e', `${item}`, true);
    });
    log('e', 'Stack trace dump:', true);
    let stack = new Error().stack?.split('\n');
    stack?.shift();
    if (!stack) stack = [];

    stack.forEach((item) => {
        log('e', `${item}`, true);
    });
    log('e', 'Process exiting.', true);
    log('e', 'Exit code 5.', true);
    log('e', 'Goodbye!', true);
    await log('CLOSE_STREAMS'); // <<<<<<<<<<<< HERE
    process.exit(5);
});

如您所知,您不能在处理退出事件时可靠地使用异步操作,因为进程会在完成之前退出。因此,我认为没有任何方法可以使用 nodejs 流可靠地执行此操作。流具有完全异步的 API 和实现,包括刷新和关闭。

在一些 Google 搜索中,我发现其他几个人 asking for the same thing 出于同样的原因,但没有提供解决方案。据我所知,这些是选项:

  1. 对流对象进行一些修改以将同步 flushAndClose() 方法添加到您的流中。您必须进入流的内部以获取缓冲区和文件句柄,并对任何剩余缓冲区执行您自己的同步写入,然后对文件句柄执行同步关闭。请注意,如果当前正在进行异步写入操作,即使这样也会出现问题。

  2. 放弃 built-in 流,只实现自己的轻量级日志文件接口,使您可以轻松地同时拥有异步写入(正常使用)和同步 flushAndClose()紧急手术 shut-down。请注意,即使当您想要执行同步关闭时当前正在处理异步写入操作,这也会出现问题。

  3. 与其使用 process.on('exit', ...) 来触发日志文件的关闭,不如在链中向上一级。无论是什么触发了您的应用程序的关闭,将其放入一个异步函数中,该函数将在调用 process.exit() 之前等待日志文件正确关闭,这样当您仍然能够处理异步时,您就可以关闭日志文件操作。

  4. 从不同的(更稳定的)进程进行日志记录。然后,此进程可以向日志记录进程发送它想要记录的内容的消息,并且该进程可以管理将日志记录信息安全地保存到磁盘,而不管源进程是否突然关闭。

注意:退出进程将自动关闭所有打开的文件选择器(OS 会为您处理)。所以,只要这是在某些致命错误情况下的边缘情况 shut-down,而不是常见的正常情况 shut-down,那么也许您在这里没有真正解决的大问题。

可能发生的最坏情况是您可能会丢失一些最近记录的数据行(如果它们尚未从流中清除)。请注意,流会尽可能立即将数据写入其描述符,因此它们通常不会累积大量缓冲数据。他们做buffer data的时候就是有新的写流发生的时候,但是之前的写还在操作。然后要写入的数据被缓冲,直到先前的写入操作完成。因此,数据永远不会与空闲流一起留在缓冲区中。这往往会最大限度地减少(但不会消除)即时 shut-down.

上的数据丢失

如果这是正常的、常规的 shut-down,那么您应该能够使用上面的选项 #3 并重塑 shut-down 的发生方式,以便您可以在需要的地方使用异步代码,这样您可以正确关闭流。