Skip to content

Commit d66bacf

Browse files
committed
fix: address PR review feedback for telemetry retry queue
- Add error handling for TelemetryStatusMonitor initialization - Fix memory leak by clearing callbacks in TelemetryClient shutdown - Remove immediate processQueue call on start to fix test timing issues - Update tests to work with the new behavior
1 parent e3ee6f7 commit d66bacf

File tree

4 files changed

+87
-31
lines changed

4 files changed

+87
-31
lines changed

packages/cloud/src/TelemetryClient.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,5 +283,8 @@ export class TelemetryClient extends BaseTelemetryClient {
283283
if (this.retryManager) {
284284
this.retryManager.stop()
285285
}
286+
// Clear callbacks to prevent memory leaks
287+
this.connectionStatusCallback = undefined
288+
this.queueSizeCallback = undefined
286289
}
287290
}

packages/cloud/src/telemetry/TelemetryRetryManager.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ export class TelemetryRetryManager {
4242
this.retryTimer = setInterval(() => {
4343
this.processQueue()
4444
}, this.config.retryIntervalMs)
45-
46-
// Process immediately on start
47-
this.processQueue()
4845
}
4946

5047
/**

packages/cloud/src/telemetry/__tests__/TelemetryRetryManager.test.ts

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,42 +51,51 @@ describe("TelemetryRetryManager", () => {
5151

5252
afterEach(() => {
5353
retryManager.stop()
54+
vi.clearAllTimers()
5455
vi.useRealTimers()
5556
})
5657

5758
describe("start/stop", () => {
5859
it("should start retry timer", async () => {
5960
retryManager.start()
6061

61-
// Wait for immediate processing
62-
await vi.runOnlyPendingTimersAsync()
62+
// Should not process immediately
63+
expect(mockQueue.getEventsForRetry).toHaveBeenCalledTimes(0)
6364

64-
// Should process immediately
65+
// Advance timer and run pending timers
66+
await vi.advanceTimersByTimeAsync(30000)
6567
expect(mockQueue.getEventsForRetry).toHaveBeenCalledTimes(1)
6668

67-
// Advance timer
68-
vi.advanceTimersByTime(30000)
69-
await vi.runOnlyPendingTimersAsync()
69+
// Advance timer again
70+
await vi.advanceTimersByTimeAsync(30000)
7071
expect(mockQueue.getEventsForRetry).toHaveBeenCalledTimes(2)
7172
})
7273

7374
it("should not start multiple timers", async () => {
7475
retryManager.start()
75-
await vi.runOnlyPendingTimersAsync()
76-
7776
retryManager.start()
7877

78+
// Advance timer
79+
await vi.advanceTimersByTimeAsync(30000)
7980
expect(mockQueue.getEventsForRetry).toHaveBeenCalledTimes(1)
81+
82+
// Advance timer again
83+
await vi.advanceTimersByTimeAsync(30000)
84+
expect(mockQueue.getEventsForRetry).toHaveBeenCalledTimes(2)
8085
})
8186

8287
it("should stop retry timer", async () => {
8388
retryManager.start()
84-
await vi.runOnlyPendingTimersAsync()
89+
90+
// Advance timer once
91+
await vi.advanceTimersByTimeAsync(30000)
92+
expect(mockQueue.getEventsForRetry).toHaveBeenCalledTimes(1)
8593

8694
retryManager.stop()
8795

88-
vi.advanceTimersByTime(60000)
89-
// Should only be called once from initial start
96+
// Advance timer again
97+
await vi.advanceTimersByTimeAsync(30000)
98+
// Should still only be called once
9099
expect(mockQueue.getEventsForRetry).toHaveBeenCalledTimes(1)
91100
})
92101
})
@@ -146,10 +155,16 @@ describe("TelemetryRetryManager", () => {
146155
mockQueue.getEventsForRetry.mockResolvedValue(events)
147156

148157
retryManager.start()
149-
await vi.runOnlyPendingTimersAsync()
158+
159+
// Advance timer to trigger processing
160+
await vi.advanceTimersByTimeAsync(30000)
150161

151162
// Should process in batches of 10
152-
expect(sendEventMock).toHaveBeenCalledTimes(25)
163+
// Filter out connection check events
164+
const actualEventCalls = sendEventMock.mock.calls.filter(
165+
(call) => (call[0].event as string) !== "telemetry_connection_check",
166+
)
167+
expect(actualEventCalls).toHaveLength(25)
153168
expect(mockQueue.updateEventAfterRetry).toHaveBeenCalledTimes(25)
154169
})
155170

@@ -168,7 +183,9 @@ describe("TelemetryRetryManager", () => {
168183
sendEventMock.mockResolvedValue(undefined)
169184

170185
retryManager.start()
171-
await vi.runOnlyPendingTimersAsync()
186+
187+
// Advance timer to trigger processing
188+
await vi.advanceTimersByTimeAsync(30000)
172189

173190
expect(mockQueue.updateEventAfterRetry).toHaveBeenCalledWith("event-1", true, undefined)
174191
})
@@ -188,7 +205,9 @@ describe("TelemetryRetryManager", () => {
188205
sendEventMock.mockRejectedValue(new Error("Network error"))
189206

190207
retryManager.start()
191-
await vi.runOnlyPendingTimersAsync()
208+
209+
// Advance timer to trigger processing
210+
await vi.advanceTimersByTimeAsync(30000)
192211

193212
expect(mockQueue.updateEventAfterRetry).toHaveBeenCalledWith("event-1", false, "Network error")
194213
})
@@ -212,14 +231,15 @@ describe("TelemetryRetryManager", () => {
212231
sendEventMock.mockRejectedValueOnce(new Error("Network error"))
213232

214233
retryManager.start()
215-
await vi.runOnlyPendingTimersAsync()
234+
235+
// Advance timer to trigger processing
236+
await vi.advanceTimersByTimeAsync(30000)
216237

217238
expect(connectionStatusCallback).toHaveBeenCalledWith(false)
218239

219240
// Now succeed
220241
sendEventMock.mockResolvedValueOnce(undefined)
221-
vi.advanceTimersByTime(30000)
222-
await vi.runOnlyPendingTimersAsync()
242+
await vi.advanceTimersByTimeAsync(30000)
223243

224244
expect(connectionStatusCallback).toHaveBeenCalledWith(true)
225245
})
@@ -228,7 +248,9 @@ describe("TelemetryRetryManager", () => {
228248
mockQueue.pruneFailedEvents.mockResolvedValue(3)
229249

230250
retryManager.start()
231-
await vi.runOnlyPendingTimersAsync()
251+
252+
// Advance timer to trigger processing
253+
await vi.advanceTimersByTimeAsync(30000)
232254

233255
expect(mockQueue.pruneFailedEvents).toHaveBeenCalled()
234256
})
@@ -259,11 +281,26 @@ describe("TelemetryRetryManager", () => {
259281
return Promise.reject(new Error("Other error"))
260282
})
261283

284+
// Mock some events to trigger processing
285+
mockQueue.getEventsForRetry.mockResolvedValue([
286+
{
287+
id: "event-1",
288+
event: {
289+
event: TelemetryEventName.TASK_CREATED,
290+
properties: { taskId: "test-1" },
291+
},
292+
timestamp: Date.now(),
293+
retryCount: 0,
294+
},
295+
])
296+
262297
retryManager.start()
263298

264-
// Advance past connection check interval (1 minute)
265-
vi.advanceTimersByTime(65000)
266-
await vi.runOnlyPendingTimersAsync()
299+
// First advance to trigger initial processing (30s)
300+
await vi.advanceTimersByTimeAsync(30000)
301+
302+
// Then advance past connection check interval (1 minute more)
303+
await vi.advanceTimersByTimeAsync(35000)
267304

268305
// Should have sent a connection check event
269306
expect(sendEventMock).toHaveBeenCalledWith(
@@ -277,11 +314,26 @@ describe("TelemetryRetryManager", () => {
277314
// All sends fail
278315
sendEventMock.mockRejectedValue(new Error("Connection failed"))
279316

317+
// Mock some events to trigger processing
318+
mockQueue.getEventsForRetry.mockResolvedValue([
319+
{
320+
id: "event-1",
321+
event: {
322+
event: TelemetryEventName.TASK_CREATED,
323+
properties: { taskId: "test-1" },
324+
},
325+
timestamp: Date.now(),
326+
retryCount: 0,
327+
},
328+
])
329+
280330
retryManager.start()
281331

282-
// Advance past connection check interval
283-
vi.advanceTimersByTime(65000)
284-
await vi.runOnlyPendingTimersAsync()
332+
// First advance to trigger initial processing (30s)
333+
await vi.advanceTimersByTimeAsync(30000)
334+
335+
// Then advance past connection check interval (1 minute more)
336+
await vi.advanceTimersByTimeAsync(35000)
285337

286338
expect(connectionStatusCallback).toHaveBeenCalledWith(false)
287339
})

src/extension.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,13 @@ export async function activate(context: vscode.ExtensionContext) {
8282
})
8383

8484
// Initialize telemetry status monitor
85-
const telemetryStatusMonitor = new TelemetryStatusMonitor(context)
86-
telemetryStatusMonitor.initialize()
87-
context.subscriptions.push(telemetryStatusMonitor)
85+
try {
86+
const telemetryStatusMonitor = new TelemetryStatusMonitor(context)
87+
telemetryStatusMonitor.initialize()
88+
context.subscriptions.push(telemetryStatusMonitor)
89+
} catch (error) {
90+
outputChannel.appendLine(`[TelemetryStatusMonitor] Failed to initialize: ${error}`)
91+
}
8892

8993
// Initialize MDM service
9094
const mdmService = await MdmService.createInstance(cloudLogger)

0 commit comments

Comments
 (0)