From 814cc8af9b82dac9337426ab7b526307f910e5e9 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 2 Mar 2023 08:03:52 -0600 Subject: [PATCH] Fixes #42 QueueTimer keeps Node process from terminating gracefully --- packages/core/src/ExceptionlessClient.ts | 3 +++ packages/core/src/Utils.ts | 10 ++++++++++ .../core/src/plugins/default/DuplicateCheckerPlugin.ts | 3 ++- packages/core/src/plugins/default/HeartbeatPlugin.ts | 3 +++ packages/core/src/queue/DefaultEventQueue.ts | 2 ++ packages/node/src/plugins/NodeLifeCyclePlugin.ts | 9 +++++++++ 6 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/core/src/ExceptionlessClient.ts b/packages/core/src/ExceptionlessClient.ts index d652c635..21f67c3f 100644 --- a/packages/core/src/ExceptionlessClient.ts +++ b/packages/core/src/ExceptionlessClient.ts @@ -7,6 +7,7 @@ import { EventContext } from "./models/EventContext.js"; import { EventPluginContext } from "./plugins/EventPluginContext.js"; import { EventPluginManager } from "./plugins/EventPluginManager.js"; import { PluginContext } from "./plugins/PluginContext.js"; +import { allowProcessToExitWithoutWaitingForTimerOrInterval } from "./Utils.js"; export class ExceptionlessClient { private _intervalId: ReturnType | undefined; @@ -92,9 +93,11 @@ export class ExceptionlessClient { void SettingsManager.updateSettings(this.config); if (initialDelay < interval) { this._timeoutId = setTimeout(updateSettings, initialDelay); + allowProcessToExitWithoutWaitingForTimerOrInterval(this._timeoutId); } this._intervalId = setInterval(updateSettings, interval); + allowProcessToExitWithoutWaitingForTimerOrInterval(this._intervalId); } } diff --git a/packages/core/src/Utils.ts b/packages/core/src/Utils.ts index 55ad91b4..52530165 100644 --- a/packages/core/src/Utils.ts +++ b/packages/core/src/Utils.ts @@ -308,3 +308,13 @@ export function toError(errorOrMessage: unknown, defaultMessage = "Unknown Error return new Error(JSON.stringify(errorOrMessage) || defaultMessage); } + + +/** + * Unrefs a timeout or interval. When called, the active Timeout object will not require the Node.js event loop to remain active + */ +export function allowProcessToExitWithoutWaitingForTimerOrInterval(timeoutOrIntervalId: ReturnType | ReturnType | undefined): void { + if (typeof timeoutOrIntervalId === "object" && "unref" in timeoutOrIntervalId) { + (timeoutOrIntervalId as { unref: () => ReturnType | ReturnType }).unref(); + } +} diff --git a/packages/core/src/plugins/default/DuplicateCheckerPlugin.ts b/packages/core/src/plugins/default/DuplicateCheckerPlugin.ts index 26ca526b..ccae12ac 100644 --- a/packages/core/src/plugins/default/DuplicateCheckerPlugin.ts +++ b/packages/core/src/plugins/default/DuplicateCheckerPlugin.ts @@ -1,6 +1,6 @@ import { InnerErrorInfo } from "../../models/data/ErrorInfo.js"; import { KnownEventDataKeys } from "../../models/Event.js"; -import { getHashCode } from "../../Utils.js"; +import { allowProcessToExitWithoutWaitingForTimerOrInterval, getHashCode } from "../../Utils.js"; import { EventPluginContext } from "../EventPluginContext.js"; import { IEventPlugin } from "../IEventPlugin.js"; @@ -25,6 +25,7 @@ export class DuplicateCheckerPlugin implements IEventPlugin { public startup(): Promise { clearInterval(this._intervalId); this._intervalId = setInterval(() => void this.submitEvents(), this._interval); + allowProcessToExitWithoutWaitingForTimerOrInterval(this._intervalId); return Promise.resolve(); } diff --git a/packages/core/src/plugins/default/HeartbeatPlugin.ts b/packages/core/src/plugins/default/HeartbeatPlugin.ts index 46a7c279..ffc543a5 100644 --- a/packages/core/src/plugins/default/HeartbeatPlugin.ts +++ b/packages/core/src/plugins/default/HeartbeatPlugin.ts @@ -1,4 +1,5 @@ import { KnownEventDataKeys } from "../../models/Event.js"; +import { allowProcessToExitWithoutWaitingForTimerOrInterval } from "../../Utils.js"; import { EventPluginContext } from "../EventPluginContext.js"; import { IEventPlugin } from "../IEventPlugin.js"; @@ -49,6 +50,8 @@ export class HeartbeatPlugin implements IEventPlugin { () => void context.client.submitSessionHeartbeat(config.currentSessionIdentifier), this._interval ); + + allowProcessToExitWithoutWaitingForTimerOrInterval(this._intervalId); } return Promise.resolve(); diff --git a/packages/core/src/queue/DefaultEventQueue.ts b/packages/core/src/queue/DefaultEventQueue.ts index 579c5729..5929ee3d 100644 --- a/packages/core/src/queue/DefaultEventQueue.ts +++ b/packages/core/src/queue/DefaultEventQueue.ts @@ -3,6 +3,7 @@ import { ILog } from "../logging/ILog.js"; import { Event } from "../models/Event.js"; import { IEventQueue } from "../queue/IEventQueue.js"; import { Response } from "../submission/Response.js"; +import { allowProcessToExitWithoutWaitingForTimerOrInterval } from "../Utils.js"; interface EventQueueItem { file: string, @@ -133,6 +134,7 @@ export class DefaultEventQueue implements IEventQueue { if (!this._queueIntervalId) { // TODO: Fix awaiting promise. this._queueIntervalId = setInterval(() => void this.onProcessQueue(), 10000); + allowProcessToExitWithoutWaitingForTimerOrInterval(this._queueIntervalId); } return Promise.resolve(); diff --git a/packages/node/src/plugins/NodeLifeCyclePlugin.ts b/packages/node/src/plugins/NodeLifeCyclePlugin.ts index 298293cc..696826cf 100644 --- a/packages/node/src/plugins/NodeLifeCyclePlugin.ts +++ b/packages/node/src/plugins/NodeLifeCyclePlugin.ts @@ -17,7 +17,16 @@ export class NodeLifeCyclePlugin implements IEventPlugin { this._client = context.client; + let processingBeforeExit: boolean = false; process.on("beforeExit", (code: number) => { + // NOTE: We need to check if we are already processing a beforeExit event + // as async work will cause the runtime to call this handler again. + if (processingBeforeExit) { + return; + } + + processingBeforeExit = true; + const message = this.getExitCodeReason(code); if (message) { void this._client?.submitLog("beforeExit", message, "Error"); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy