diff --git a/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts b/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts index 5869071d4a6cd..e006522797a8c 100644 --- a/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts +++ b/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts @@ -251,9 +251,10 @@ export class GarbageCollector { debug(`Parsing through ${numImages} images in batches`); + printer.start(); + for await (const batch of this.readRepoInBatches(ecr, repo, batchSize, currentTime)) { await backgroundStackRefresh.noOlderThan(600_000); // 10 mins - printer.start(); const { included: isolated, excluded: notIsolated } = partition(batch, asset => !asset.tags.some(t => activeAssets.contains(t))); @@ -323,12 +324,13 @@ export class GarbageCollector { debug(`Parsing through ${numObjects} objects in batches`); + printer.start(); + // Process objects in batches of 1000 // This is the batch limit of s3.DeleteObject and we intend to optimize for the "worst case" scenario // where gc is run for the first time on a long-standing bucket where ~100% of objects are isolated. for await (const batch of this.readBucketInBatches(s3, bucket, batchSize, currentTime)) { await backgroundStackRefresh.noOlderThan(600_000); // 10 mins - printer.start(); const { included: isolated, excluded: notIsolated } = partition(batch, asset => !activeAssets.contains(asset.fileName())); diff --git a/packages/aws-cdk/lib/api/garbage-collection/progress-printer.ts b/packages/aws-cdk/lib/api/garbage-collection/progress-printer.ts index 9790dadeb0c5a..bca2eb7fb7812 100644 --- a/packages/aws-cdk/lib/api/garbage-collection/progress-printer.ts +++ b/packages/aws-cdk/lib/api/garbage-collection/progress-printer.ts @@ -1,6 +1,7 @@ import * as chalk from 'chalk'; import { GcAsset as GCAsset } from './garbage-collector'; import { info } from '../../logging'; +import { ToolkitError } from '../../toolkit/error'; export class ProgressPrinter { private totalAssets: number; @@ -41,6 +42,13 @@ export class ProgressPrinter { } public start() { + // If there is already a running setInterval, throw an error. + // This is because if this.setInterval is reassigned to another setInterval, + // the original setInterval remains and can no longer be cleared. + if (this.setInterval) { + throw new ToolkitError('ProgressPrinter is already running. Stop it first using the stop() method before starting it again.'); + } + this.setInterval = setInterval(() => { if (!this.isPaused) { this.print(); diff --git a/packages/aws-cdk/test/api/garbage-collection.test.ts b/packages/aws-cdk/test/api/garbage-collection.test.ts index d6a55dcf951ff..158b6b0f7c53d 100644 --- a/packages/aws-cdk/test/api/garbage-collection.test.ts +++ b/packages/aws-cdk/test/api/garbage-collection.test.ts @@ -20,6 +20,7 @@ import { BackgroundStackRefresh, BackgroundStackRefreshProps, } from '../../lib/api/garbage-collection/stack-refresh'; +import { ProgressPrinter } from '../../lib/api/garbage-collection/progress-printer'; import { BatchDeleteImageCommand, BatchGetImageCommand, @@ -1002,6 +1003,33 @@ describe('BackgroundStackRefresh', () => { }); }); +describe('ProgressPrinter', () => { + let progressPrinter: ProgressPrinter; + let setInterval: jest.SpyInstance; + let clearInterval: jest.SpyInstance; + + beforeEach(() => { + jest.useFakeTimers(); + setInterval = jest.spyOn(global, 'setInterval'); + clearInterval = jest.spyOn(global, 'clearInterval'); + + progressPrinter = new ProgressPrinter(0, 1000); + }); + + afterEach(() => { + jest.clearAllTimers(); + setInterval.mockRestore(); + clearInterval.mockRestore(); + }); + + test('throws if start is called twice without stop', () => { + progressPrinter.start(); + + expect(setInterval).toHaveBeenCalledTimes(1); + expect(() => progressPrinter.start()).toThrow('ProgressPrinter is already running. Stop it first using the stop() method before starting it again.'); + }); +}); + function daysInThePast(days: number): Date { const d = new Date(); d.setDate(d.getDate() - days);