Skip to content

Commit 6a9737e

Browse files
committed
WIP - Ability to suspend/resume all client timers and plugins.
1 parent 317d751 commit 6a9737e

File tree

8 files changed

+91
-32
lines changed

8 files changed

+91
-32
lines changed

packages/core/src/ExceptionlessClient.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,16 @@ import { UserDescription } from "./models/data/UserDescription.js";
66
import { ContextData } from "./plugins/ContextData.js";
77
import { EventPluginContext } from "./plugins/EventPluginContext.js";
88
import { EventPluginManager } from "./plugins/EventPluginManager.js";
9+
import { PluginContext } from "./plugins/PluginContext.js";
910

1011
export class ExceptionlessClient {
1112
private _intervalId: any;
1213
private _timeoutId: any;
1314

1415
public constructor(public config: Configuration) { }
1516

16-
/*
17-
client.startup(c => {
18-
c.services.storage = new InMemoryStorageProvider(c)''
19-
})*/
2017
/** Resume background submission, resume any timers. */
21-
public startup(configurationOrApiKey?: (config: Configuration) => void | string): Promise<void> {
18+
public async startup(configurationOrApiKey?: (config: Configuration) => void | string): Promise<void> {
2219
if (configurationOrApiKey) {
2320
EventPluginManager.addDefaultPlugins(this.config);
2421

@@ -34,29 +31,28 @@ export class ExceptionlessClient {
3431
}
3532

3633
this.updateSettingsTimer(configurationOrApiKey ? 5000 : 0);
37-
// TODO: resume plugins
38-
// TODO: resume queue
39-
return Promise.resolve();
34+
await EventPluginManager.startup(new PluginContext(this));
35+
await this.processQueue();
4036
}
4137

4238
/** Submit events, pause any timers and go into low power mode. */
4339
public async suspend(): Promise<void> {
44-
// TODO: Turn off timers.
45-
this.updateSettingsTimer(0);
46-
// TODO: suspend plugins / trigger dedupe plugin to submit...
40+
await EventPluginManager.suspend(new PluginContext(this));
4741
await this.processQueue();
42+
await this.config.queue.suspend();
43+
this.updateSettingsTimer(0, -1);
4844
}
4945

5046
public async processQueue(): Promise<void> {
5147
await this.config.queue.process();
5248
}
5349

5450
// TODO: Look into better async scheduling..
55-
private updateSettingsTimer(initialDelay?: number) {
51+
private updateSettingsTimer(initialDelay: number = 0, updateWhenIdleInterval?: number) {
5652
this._timeoutId = clearTimeout(this._timeoutId);
5753
this._intervalId = clearInterval(this._intervalId);
5854

59-
const interval = this.config.updateSettingsWhenIdleInterval;
55+
const interval = updateWhenIdleInterval || this.config.updateSettingsWhenIdleInterval;
6056
if (interval > 0) {
6157
this.config.log.info(`Update settings every ${interval}ms (${initialDelay || 0}ms delay)`);
6258
// TODO: Fix awaiting promise.

packages/core/src/plugins/EventPluginManager.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,37 @@ import { ModuleInfoPlugin } from "./default/ModuleInfoPlugin.js";
88
import { RequestInfoPlugin } from "./default/RequestInfoPlugin.js";
99
import { SubmissionMethodPlugin } from "./default/SubmissionMethodPlugin.js";
1010
import { EventPluginContext } from "./EventPluginContext.js";
11+
import { PluginContext } from "./PluginContext.js";
1112

1213
export class EventPluginManager {
14+
public static async startup(context: PluginContext): Promise<void> {
15+
for (const plugin of context.client.config.plugins) {
16+
if (!plugin.startup) {
17+
continue;
18+
}
19+
20+
try {
21+
await plugin.startup(context);
22+
} catch (ex) {
23+
context.log.error(`Error running plugin startup"${plugin.name}": ${ex.message}`);
24+
}
25+
}
26+
}
27+
28+
public static async suspend(context: PluginContext): Promise<void> {
29+
for (const plugin of context.client.config.plugins) {
30+
if (!plugin.suspend) {
31+
continue;
32+
}
33+
34+
try {
35+
await plugin.suspend(context);
36+
} catch (ex) {
37+
context.log.error(`Error running plugin suspend"${plugin.name}": ${ex.message}`);
38+
}
39+
}
40+
}
41+
1342
public static async run(context: EventPluginContext): Promise<void> {
1443
for (const plugin of context.client.config.plugins) {
1544
if (context.cancelled) {

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { KnownEventDataKeys } from "../../models/Event.js";
33
import { getHashCode } from "../../Utils.js";
44
import { EventPluginContext } from "../EventPluginContext.js";
55
import { IEventPlugin } from "../IEventPlugin.js";
6+
import { PluginContext } from "../PluginContext.js";
67

78
export class DuplicateCheckerPlugin implements IEventPlugin {
89
public priority: number = 1010;
@@ -11,6 +12,7 @@ export class DuplicateCheckerPlugin implements IEventPlugin {
1112
private _mergedEvents: MergedEvent[] = [];
1213
private _processedHashCodes: TimestampedHash[] = [];
1314
private _getCurrentTime: () => number;
15+
private _intervalId: any;
1416
private _interval: number;
1517

1618
constructor(
@@ -19,13 +21,23 @@ export class DuplicateCheckerPlugin implements IEventPlugin {
1921
) {
2022
this._getCurrentTime = getCurrentTime;
2123
this._interval = interval;
24+
}
2225

23-
// TODO: Can we pause this? What about on shutdown,
24-
setInterval(() => {
26+
public startup(context: PluginContext): Promise<void> {
27+
this._intervalId = clearInterval(this._intervalId);
28+
this._intervalId = setInterval(() => {
2529
while (this._mergedEvents.length > 0) {
2630
this._mergedEvents.shift().resubmit();
2731
}
28-
}, interval);
32+
}, this._interval);
33+
34+
return Promise.resolve();
35+
}
36+
37+
public suspend(context: PluginContext): Promise<void> {
38+
this._intervalId = clearInterval(this._intervalId);
39+
// TODO: Resubmit events.
40+
return Promise.resolve();
2941
}
3042

3143
public run(context: EventPluginContext): Promise<void> {
@@ -50,9 +62,7 @@ export class DuplicateCheckerPlugin implements IEventPlugin {
5062
const count = context.event.count || 1;
5163
const now = this._getCurrentTime();
5264

53-
const merged = this._mergedEvents.filter((s) =>
54-
s.hashCode === hashCode
55-
)[0];
65+
const merged = this._mergedEvents.filter((s) => s.hashCode === hashCode)[0];
5666
if (merged) {
5767
merged.incrementCount(count);
5868
merged.updateDate(context.event.date);
@@ -72,9 +82,7 @@ export class DuplicateCheckerPlugin implements IEventPlugin {
7282
}
7383

7484
if (!context.cancelled) {
75-
context.log.trace(
76-
"Enqueueing event with hash: " + hashCode + "to cache.",
77-
);
85+
context.log.trace(`Enqueueing event with hash: ${hashCode} to cache`);
7886
this._processedHashCodes.push({ hash: hashCode, timestamp: now });
7987

8088
// Only keep the last 50 recent errors.

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { UserInfo } from "../../models/data/UserInfo.js";
22
import { KnownEventDataKeys } from "../../models/Event.js";
33
import { EventPluginContext } from "../EventPluginContext.js";
44
import { IEventPlugin } from "../IEventPlugin.js";
5+
import { PluginContext } from "../PluginContext.js";
56

67
export class HeartbeatPlugin implements IEventPlugin {
78
public priority: number = 100;
@@ -11,12 +12,23 @@ export class HeartbeatPlugin implements IEventPlugin {
1112
private _intervalId: any;
1213

1314
constructor(heartbeatInterval: number = 30000) {
14-
// TODO: Can we pause this? What about on shutdown,
1515
this._interval = heartbeatInterval >= 30000 ? heartbeatInterval : 60000;
1616
}
1717

18+
public startup(context: PluginContext): Promise<void> {
19+
this._intervalId = clearInterval(this._intervalId);
20+
// TODO: Should we submit a session start?
21+
return Promise.resolve();
22+
}
23+
24+
public suspend(context: PluginContext): Promise<void> {
25+
this._intervalId = clearInterval(this._intervalId);
26+
// TODO: Should we submit a session end?
27+
return Promise.resolve();
28+
}
29+
1830
public run(context: EventPluginContext): Promise<void> {
19-
clearInterval(this._intervalId);
31+
this._intervalId = clearInterval(this._intervalId);
2032

2133
const user: UserInfo = context.event.data[KnownEventDataKeys.UserInfo];
2234
if (user && user.identity) {

packages/core/src/queue/DefaultEventQueue.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ export class DefaultEventQueue implements IEventQueue {
4141
* @type {Timer}
4242
* @private
4343
*/
44-
private _queueTimer: any;
44+
private _queueTimerId: any;
4545

4646
constructor(private config: Configuration) {}
4747

48+
/** Enqueue an event and resumes any queue timers */
4849
public enqueue(event: Event): void {
4950
const eventWillNotBeQueued: string = "The event will not be queued."; // optimization for minifier.
5051
const config: Configuration = this.config; // Optimization for minifier.
@@ -78,6 +79,7 @@ export class DefaultEventQueue implements IEventQueue {
7879
}
7980
}
8081

82+
/** Processes all events in the queue and resumes any queue timers */
8183
public async process(): Promise<void> {
8284
const queueNotProcessed: string = "The queue will not be processed."; // optimization for minifier.
8385
const config: Configuration = this.config; // Optimization for minifier.
@@ -121,6 +123,13 @@ export class DefaultEventQueue implements IEventQueue {
121123
}
122124
}
123125

126+
/** Suspends queue timers */
127+
public suspend(): Promise<void> {
128+
this._queueTimerId = clearInterval(this._queueTimerId);
129+
return Promise.resolve();
130+
}
131+
132+
/** Suspends processing of events for a specific duration */
124133
public suspendProcessing(
125134
durationInMinutes?: number,
126135
discardFutureQueuedItems?: boolean,
@@ -148,9 +157,7 @@ export class DefaultEventQueue implements IEventQueue {
148157
}
149158

150159
// TODO: See if this makes sense.
151-
public onEventsPosted(
152-
handler: (events: Event[], response: Response<void>) => void,
153-
): void {
160+
public onEventsPosted(handler: (events: Event[], response: Response<void>) => void): void {
154161
handler && this._handlers.push(handler);
155162
}
156163

@@ -171,9 +178,9 @@ export class DefaultEventQueue implements IEventQueue {
171178
}
172179

173180
private ensureQueueTimer(): void {
174-
if (!this._queueTimer) {
181+
if (!this._queueTimerId) {
175182
// TODO: Fix awaiting promise.
176-
this._queueTimer = setInterval(() => void this.onProcessQueue(), 10000);
183+
this._queueTimerId = setInterval(() => void this.onProcessQueue(), 10000);
177184
}
178185
}
179186

packages/core/src/queue/IEventQueue.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ import { Event } from "../models/Event.js";
22
import { Response } from "../submission/Response.js";
33

44
export interface IEventQueue {
5+
/** Enqueue an event and resumes any queue timers */
56
enqueue(event: Event): void;
7+
/** Processes all events in the queue and resumes any queue timers */
68
process(): Promise<void>;
9+
/** Suspends queue timers */
10+
suspend(): Promise<void>;
11+
/** Suspends processing of events for a specific duration */
712
suspendProcessing(durationInMinutes?: number, discardFutureQueuedItems?: boolean, clearQueue?: boolean): void;
813
// TODO: See if this makes sense.
914
onEventsPosted(handler: (events: Event[], response: Response<void>) => void): void;

packages/node/src/configuration/NodeConfiguration.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { Configuration, SettingsManager } from "@exceptionless/core";
22
import { NodeFileStorageProvider } from "../storage/NodeFileStorageProvider.js";
33

44
export class NodeConfiguration extends Configuration {
5-
public useLocalStorage(): void {
6-
this.storage = new NodeFileStorageProvider();
5+
public useLocalStorage(folder?: string): void {
6+
// TODO: This should be using the first x chars of the api key for the prefix.
7+
this.storage = new NodeFileStorageProvider(folder);
78
SettingsManager.applySavedServerSettings(this);
89
this.changed();
910
}

packages/node/src/storage/NodeFileStorageProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export class NodeFileStorageProvider implements IStorageProvider {
99
public queue: IStorage;
1010
public settings: IStorage;
1111

12+
// TODO: If you can specify a folder why would we have a prefix?
1213
constructor(folder?: string, prefix?: string, maxQueueItems: number = 250) {
1314
this.queue = new NodeFileStorage("q", folder, prefix, maxQueueItems);
1415
this.settings = new NodeFileStorage("settings", folder, prefix, 1);

0 commit comments

Comments
 (0)