From 550eb3a46edc4819692be18ec0fa8be2b1c0d984 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Tue, 3 Jun 2025 10:34:10 +0100 Subject: [PATCH] fix(toolkit-lib): tracing logs on watch action does not close immediately --- .../toolkit-lib/lib/api/logs-monitor/logs-monitor.ts | 7 +++++-- packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts | 3 +++ .../@aws-cdk/toolkit-lib/test/actions/watch.test.ts | 11 ++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/logs-monitor/logs-monitor.ts b/packages/@aws-cdk/toolkit-lib/lib/api/logs-monitor/logs-monitor.ts index d1fb28796..b3da210e4 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/logs-monitor/logs-monitor.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/logs-monitor/logs-monitor.ts @@ -62,7 +62,9 @@ export class CloudWatchLogEventMonitor { private readonly pollingInterval: number = 2_000; public monitorId?: string; + private readonly ioHelper: IoHelper; + private currentTick?: NodeJS.Timeout; constructor(props: CloudWatchLogEventMonitorProps) { this.startTime = props.startTime?.getTime() ?? Date.now(); @@ -80,8 +82,8 @@ export class CloudWatchLogEventMonitor { logGroupNames: this.logGroupNames(), })); + // tick schedules the next tick as well await this.tick(); - this.scheduleNextTick(); } /** @@ -97,6 +99,7 @@ export class CloudWatchLogEventMonitor { const oldMonitorId = this.monitorId!; this.monitorId = undefined; this.startTime = Date.now(); + clearTimeout(this.currentTick); await this.ioHelper.notify(IO.CDK_TOOLKIT_I5034.msg('Stopped monitoring log groups', { monitor: oldMonitorId, @@ -140,7 +143,7 @@ export class CloudWatchLogEventMonitor { return; } - setTimeout(() => void this.tick(), this.pollingInterval); + this.currentTick = setTimeout(() => void this.tick(), this.pollingInterval); } private async tick(): Promise { diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index e344c545c..2ba99bb6e 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -973,6 +973,9 @@ export class Toolkit extends CloudAssemblySourceBuilder { return { async dispose() { + // stop the logs monitor, if it exists + await cloudWatchLogMonitor?.deactivate(); + // close the watcher itself await watcher.close(); // Prevents Node from staying alive. There is no 'end' event that the watcher emits // that we can know it's definitely done, so best we can do is tell it to stop watching, diff --git a/packages/@aws-cdk/toolkit-lib/test/actions/watch.test.ts b/packages/@aws-cdk/toolkit-lib/test/actions/watch.test.ts index dfe850296..90337581c 100644 --- a/packages/@aws-cdk/toolkit-lib/test/actions/watch.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/actions/watch.test.ts @@ -161,7 +161,7 @@ describe('watch', () => { // GIVEN const cx = await builderFixture(toolkit, 'stack-with-role'); ioHost.level = 'debug'; - await toolkit.watch(cx, { + const watcher = await toolkit.watch(cx, { include: [], traceLogs: true, }); @@ -174,8 +174,13 @@ describe('watch', () => { cloudWatchLogMonitor: expect.anything(), // Not undefined })); - // Deactivate the cloudWatchLogMonitor that we created, otherwise the tests won't exit - (deploySpy.mock.calls[0]?.[2] as any).cloudWatchLogMonitor?.deactivate(); + const logMonitorSpy = jest.spyOn((deploySpy.mock.calls[0]?.[2] as any).cloudWatchLogMonitor, 'deactivate'); + + // Deactivate the watcher and cloudWatchLogMonitor that we created, otherwise the tests won't exit + await watcher.dispose(); + + // ensure the log monitor has been closed + expect(logMonitorSpy).toHaveBeenCalled(); }); test('watch returns an object that can be used to stop the watch', async () => {