Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions src/azureMonitor/metrics/quickpulse/liveMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export class LiveMetrics {
private derivedMetricProjection: Projection = new Projection();
private validator: Validator = new Validator();
private filter: Filter = new Filter();
private _isShutdown: boolean = false;
// type: Map<telemetryType, Map<id, FilterConjunctionGroupInfo[]>>
private validDocumentFilterConjuctionGroupInfos: Map<
string,
Expand Down Expand Up @@ -203,10 +204,15 @@ export class LiveMetrics {
}

public shutdown(): void {
this.meterProvider?.shutdown();
this._isShutdown = true;
clearTimeout(this.handle as any);
this.deactivateMetrics();
}

private async goQuickpulse(): Promise<void> {
if (this._isShutdown) {
return;
}
if (!this.isCollectingData) {
// If not collecting, Ping
try {
Comment thread
hectorhdzg marked this conversation as resolved.
Expand All @@ -223,15 +229,20 @@ export class LiveMetrics {
this.quickPulseDone(undefined);
}

this.handle = <any>setTimeout(this.goQuickpulse.bind(this), this.pingInterval);
this.handle.unref();
if (!this._isShutdown) {
this.handle = <any>setTimeout(this.goQuickpulse.bind(this), this.pingInterval);
this.handle.unref();
}
}
if (this.isCollectingData) {
if (this.isCollectingData && !this._isShutdown) {
this.activateMetrics({ collectionInterval: this.postInterval });
}
}

private async quickPulseDone(response: QuickpulseResponse | undefined): Promise<void> {
if (this._isShutdown) {
return;
}
if (!response) {
if (!this.isCollectingData) {
if (Date.now() - this.lastSuccessTime >= MAX_PING_WAIT_TIME) {
Expand Down Expand Up @@ -259,8 +270,10 @@ export class LiveMetrics {
this.etag = "";
this.deactivateMetrics();

this.handle = <any>setTimeout(this.goQuickpulse.bind(this), this.pingInterval);
this.handle.unref();
if (!this._isShutdown) {
this.handle = <any>setTimeout(this.goQuickpulse.bind(this), this.pingInterval);
this.handle.unref();
}
}

const endpointRedirect = response.xMsQpsServiceEndpointRedirectV2;
Expand Down
65 changes: 65 additions & 0 deletions test/internal/unit/metrics/liveMetrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,4 +671,69 @@ describe("#LiveMetrics", () => {
["testScope1"],
);
});

it("shutdown should clear the polling timer", () => {
const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
const handle = autoCollect["handle"];
autoCollect.shutdown();
expect(clearTimeoutSpy).toHaveBeenCalledWith(handle);
clearTimeoutSpy.mockRestore();
});

it("shutdown should set _isShutdown flag", () => {
expect(autoCollect["_isShutdown"]).toBe(false);
autoCollect.shutdown();
expect(autoCollect["_isShutdown"]).toBe(true);
});

it("shutdown should deactivate metrics", () => {
autoCollect.activateMetrics({ collectionInterval: 100 });
expect(autoCollect["meterProvider"]).toBeDefined();
autoCollect.shutdown();
expect(autoCollect["meterProvider"]).toBeUndefined();
});

it("goQuickpulse should not reschedule after shutdown", async () => {
autoCollect.shutdown();
const setTimeoutSpy = vi.spyOn(global, "setTimeout");
await autoCollect["goQuickpulse"]();
// goQuickpulse should early-return without scheduling a new timer
expect(setTimeoutSpy).not.toHaveBeenCalled();
setTimeoutSpy.mockRestore();
});

it("goQuickpulse should not reschedule if shutdown called during ping", async () => {
// Simulate shutdown happening while the ping request is in-flight
vi.spyOn(autoCollect["pingSender"], "isSubscribed").mockImplementation(async () => {
// Shutdown is called while we are awaiting the ping
autoCollect.shutdown();
return undefined as any;
});
autoCollect["_isShutdown"] = false; // reset so goQuickpulse enters the body
autoCollect["isCollectingData"] = false;
const setTimeoutSpy = vi.spyOn(global, "setTimeout");
await autoCollect["goQuickpulse"]();
expect(setTimeoutSpy).not.toHaveBeenCalled();
setTimeoutSpy.mockRestore();
});

it("quickPulseDone should not reschedule after shutdown", async () => {
autoCollect.shutdown();
const setTimeoutSpy = vi.spyOn(global, "setTimeout");
await autoCollect["quickPulseDone"]({
xMsQpsSubscribed: "false",
xMsQpsConfigurationEtag: undefined,
} as any);
expect(setTimeoutSpy).not.toHaveBeenCalled();
setTimeoutSpy.mockRestore();
});

it("shutdown should be idempotent", () => {
autoCollect.activateMetrics({ collectionInterval: 100 });
autoCollect.shutdown();
// Second shutdown should not throw
expect(() => autoCollect.shutdown()).not.toThrow();
expect(autoCollect["_isShutdown"]).toBe(true);
expect(autoCollect["meterProvider"]).toBeUndefined();
});
});
Loading