Skip to content

Commit 814cc8a

Browse files
committed
Fixes #42 QueueTimer keeps Node process from terminating gracefully
1 parent 859f157 commit 814cc8a

File tree

6 files changed

+29
-1
lines changed

6 files changed

+29
-1
lines changed

packages/core/src/ExceptionlessClient.ts

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { EventContext } from "./models/EventContext.js";
77
import { EventPluginContext } from "./plugins/EventPluginContext.js";
88
import { EventPluginManager } from "./plugins/EventPluginManager.js";
99
import { PluginContext } from "./plugins/PluginContext.js";
10+
import { allowProcessToExitWithoutWaitingForTimerOrInterval } from "./Utils.js";
1011

1112
export class ExceptionlessClient {
1213
private _intervalId: ReturnType<typeof setInterval> | undefined;
@@ -92,9 +93,11 @@ export class ExceptionlessClient {
9293
void SettingsManager.updateSettings(this.config);
9394
if (initialDelay < interval) {
9495
this._timeoutId = setTimeout(updateSettings, initialDelay);
96+
allowProcessToExitWithoutWaitingForTimerOrInterval(this._timeoutId);
9597
}
9698

9799
this._intervalId = setInterval(updateSettings, interval);
100+
allowProcessToExitWithoutWaitingForTimerOrInterval(this._intervalId);
98101
}
99102
}
100103

packages/core/src/Utils.ts

+10
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,13 @@ export function toError(errorOrMessage: unknown, defaultMessage = "Unknown Error
308308

309309
return new Error(JSON.stringify(errorOrMessage) || defaultMessage);
310310
}
311+
312+
313+
/**
314+
* Unrefs a timeout or interval. When called, the active Timeout object will not require the Node.js event loop to remain active
315+
*/
316+
export function allowProcessToExitWithoutWaitingForTimerOrInterval(timeoutOrIntervalId: ReturnType<typeof setTimeout> | ReturnType<typeof setInterval> | undefined): void {
317+
if (typeof timeoutOrIntervalId === "object" && "unref" in timeoutOrIntervalId) {
318+
(timeoutOrIntervalId as { unref: () => ReturnType<typeof setTimeout> | ReturnType<typeof setInterval> }).unref();
319+
}
320+
}

packages/core/src/plugins/default/DuplicateCheckerPlugin.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { InnerErrorInfo } from "../../models/data/ErrorInfo.js";
22
import { KnownEventDataKeys } from "../../models/Event.js";
3-
import { getHashCode } from "../../Utils.js";
3+
import { allowProcessToExitWithoutWaitingForTimerOrInterval, getHashCode } from "../../Utils.js";
44
import { EventPluginContext } from "../EventPluginContext.js";
55
import { IEventPlugin } from "../IEventPlugin.js";
66

@@ -25,6 +25,7 @@ export class DuplicateCheckerPlugin implements IEventPlugin {
2525
public startup(): Promise<void> {
2626
clearInterval(this._intervalId);
2727
this._intervalId = setInterval(() => void this.submitEvents(), this._interval);
28+
allowProcessToExitWithoutWaitingForTimerOrInterval(this._intervalId);
2829
return Promise.resolve();
2930
}
3031

packages/core/src/plugins/default/HeartbeatPlugin.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { KnownEventDataKeys } from "../../models/Event.js";
2+
import { allowProcessToExitWithoutWaitingForTimerOrInterval } from "../../Utils.js";
23
import { EventPluginContext } from "../EventPluginContext.js";
34
import { IEventPlugin } from "../IEventPlugin.js";
45

@@ -49,6 +50,8 @@ export class HeartbeatPlugin implements IEventPlugin {
4950
() => void context.client.submitSessionHeartbeat(<string>config.currentSessionIdentifier),
5051
this._interval
5152
);
53+
54+
allowProcessToExitWithoutWaitingForTimerOrInterval(this._intervalId);
5255
}
5356

5457
return Promise.resolve();

packages/core/src/queue/DefaultEventQueue.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ILog } from "../logging/ILog.js";
33
import { Event } from "../models/Event.js";
44
import { IEventQueue } from "../queue/IEventQueue.js";
55
import { Response } from "../submission/Response.js";
6+
import { allowProcessToExitWithoutWaitingForTimerOrInterval } from "../Utils.js";
67

78
interface EventQueueItem {
89
file: string,
@@ -133,6 +134,7 @@ export class DefaultEventQueue implements IEventQueue {
133134
if (!this._queueIntervalId) {
134135
// TODO: Fix awaiting promise.
135136
this._queueIntervalId = setInterval(() => void this.onProcessQueue(), 10000);
137+
allowProcessToExitWithoutWaitingForTimerOrInterval(this._queueIntervalId);
136138
}
137139

138140
return Promise.resolve();

packages/node/src/plugins/NodeLifeCyclePlugin.ts

+9
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,16 @@ export class NodeLifeCyclePlugin implements IEventPlugin {
1717

1818
this._client = context.client;
1919

20+
let processingBeforeExit: boolean = false;
2021
process.on("beforeExit", (code: number) => {
22+
// NOTE: We need to check if we are already processing a beforeExit event
23+
// as async work will cause the runtime to call this handler again.
24+
if (processingBeforeExit) {
25+
return;
26+
}
27+
28+
processingBeforeExit = true;
29+
2130
const message = this.getExitCodeReason(code);
2231
if (message) {
2332
void this._client?.submitLog("beforeExit", message, "Error");

0 commit comments

Comments
 (0)