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 出于同样的原因,但没有提供解决方案。据我所知,这些是选项:
对流对象进行一些修改以将同步 flushAndClose()
方法添加到您的流中。您必须进入流的内部以获取缓冲区和文件句柄,并对任何剩余缓冲区执行您自己的同步写入,然后对文件句柄执行同步关闭。请注意,如果当前正在进行异步写入操作,即使这样也会出现问题。
放弃 built-in 流,只实现自己的轻量级日志文件接口,使您可以轻松地同时拥有异步写入(正常使用)和同步 flushAndClose()
紧急手术 shut-down。请注意,即使当您想要执行同步关闭时当前正在处理异步写入操作,这也会出现问题。
与其使用 process.on('exit', ...)
来触发日志文件的关闭,不如在链中向上一级。无论是什么触发了您的应用程序的关闭,将其放入一个异步函数中,该函数将在调用 process.exit()
之前等待日志文件正确关闭,这样当您仍然能够处理异步时,您就可以关闭日志文件操作。
从不同的(更稳定的)进程进行日志记录。然后,此进程可以向日志记录进程发送它想要记录的内容的消息,并且该进程可以管理将日志记录信息安全地保存到磁盘,而不管源进程是否突然关闭。
注意:退出进程将自动关闭所有打开的文件选择器(OS 会为您处理)。所以,只要这是在某些致命错误情况下的边缘情况 shut-down,而不是常见的正常情况 shut-down,那么也许您在这里没有真正解决的大问题。
可能发生的最坏情况是您可能会丢失一些最近记录的数据行(如果它们尚未从流中清除)。请注意,流会尽可能立即将数据写入其描述符,因此它们通常不会累积大量缓冲数据。他们做buffer data的时候就是有新的写流发生的时候,但是之前的写还在操作。然后要写入的数据被缓冲,直到先前的写入操作完成。因此,数据永远不会与空闲流一起留在缓冲区中。这往往会最大限度地减少(但不会消除)即时 shut-down.
上的数据丢失
如果这是正常的、常规的 shut-down,那么您应该能够使用上面的选项 #3 并重塑 shut-down 的发生方式,以便您可以在需要的地方使用异步代码,这样您可以正确关闭流。
在 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 出于同样的原因,但没有提供解决方案。据我所知,这些是选项:
对流对象进行一些修改以将同步
flushAndClose()
方法添加到您的流中。您必须进入流的内部以获取缓冲区和文件句柄,并对任何剩余缓冲区执行您自己的同步写入,然后对文件句柄执行同步关闭。请注意,如果当前正在进行异步写入操作,即使这样也会出现问题。放弃 built-in 流,只实现自己的轻量级日志文件接口,使您可以轻松地同时拥有异步写入(正常使用)和同步
flushAndClose()
紧急手术 shut-down。请注意,即使当您想要执行同步关闭时当前正在处理异步写入操作,这也会出现问题。与其使用
process.on('exit', ...)
来触发日志文件的关闭,不如在链中向上一级。无论是什么触发了您的应用程序的关闭,将其放入一个异步函数中,该函数将在调用process.exit()
之前等待日志文件正确关闭,这样当您仍然能够处理异步时,您就可以关闭日志文件操作。从不同的(更稳定的)进程进行日志记录。然后,此进程可以向日志记录进程发送它想要记录的内容的消息,并且该进程可以管理将日志记录信息安全地保存到磁盘,而不管源进程是否突然关闭。
注意:退出进程将自动关闭所有打开的文件选择器(OS 会为您处理)。所以,只要这是在某些致命错误情况下的边缘情况 shut-down,而不是常见的正常情况 shut-down,那么也许您在这里没有真正解决的大问题。
可能发生的最坏情况是您可能会丢失一些最近记录的数据行(如果它们尚未从流中清除)。请注意,流会尽可能立即将数据写入其描述符,因此它们通常不会累积大量缓冲数据。他们做buffer data的时候就是有新的写流发生的时候,但是之前的写还在操作。然后要写入的数据被缓冲,直到先前的写入操作完成。因此,数据永远不会与空闲流一起留在缓冲区中。这往往会最大限度地减少(但不会消除)即时 shut-down.
上的数据丢失如果这是正常的、常规的 shut-down,那么您应该能够使用上面的选项 #3 并重塑 shut-down 的发生方式,以便您可以在需要的地方使用异步代码,这样您可以正确关闭流。