Skip to content

Commit 040238e

Browse files
authored
fix(cli): hotswapping is slow for many resources deployed at once (#19081)
In the `LazyListStackResources` class, we were incorrectly caching the results of the ListStackResources CloudFormation API call. This resulted in executing redundant calls, and very visibly impacted the performance of hotswapping when the Stack included many resources. In manual testing, this change brought down deployment time of a large Stack from 80 to 7 seconds. Closes #19021 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent e2f6918 commit 040238e

File tree

2 files changed

+41
-12
lines changed

2 files changed

+41
-12
lines changed

packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,30 @@ export interface ListStackResources {
77
}
88

99
export class LazyListStackResources implements ListStackResources {
10-
private stackResources: AWS.CloudFormation.StackResourceSummary[] | undefined;
10+
private stackResources: Promise<AWS.CloudFormation.StackResourceSummary[]> | undefined;
1111

1212
constructor(private readonly sdk: ISDK, private readonly stackName: string) {
1313
}
1414

1515
public async listStackResources(): Promise<AWS.CloudFormation.StackResourceSummary[]> {
1616
if (this.stackResources === undefined) {
17-
this.stackResources = await this.getStackResources();
17+
this.stackResources = this.getStackResources(undefined);
1818
}
1919
return this.stackResources;
2020
}
2121

22-
private async getStackResources(): Promise<AWS.CloudFormation.StackResourceSummary[]> {
22+
private async getStackResources(nextToken: string | undefined): Promise<AWS.CloudFormation.StackResourceSummary[]> {
2323
const ret = new Array<AWS.CloudFormation.StackResourceSummary>();
24-
let nextToken: string | undefined;
25-
do {
26-
const stackResourcesResponse = await this.sdk.cloudFormation().listStackResources({
27-
StackName: this.stackName,
28-
NextToken: nextToken,
29-
}).promise();
24+
return this.sdk.cloudFormation().listStackResources({
25+
StackName: this.stackName,
26+
NextToken: nextToken,
27+
}).promise().then(async stackResourcesResponse => {
3028
ret.push(...(stackResourcesResponse.StackResourceSummaries ?? []));
31-
nextToken = stackResourcesResponse.NextToken;
32-
} while (nextToken);
33-
return ret;
29+
if (stackResourcesResponse.NextToken) {
30+
ret.push(...await this.getStackResources(stackResourcesResponse.NextToken));
31+
}
32+
return ret;
33+
});
3434
}
3535
}
3636

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as AWS from 'aws-sdk';
2+
import { LazyListStackResources } from '../../lib/api/evaluate-cloudformation-template';
3+
import { MockSdk } from '../util/mock-sdk';
4+
5+
describe('Lazy ListStackResources', () => {
6+
test('correctly caches calls to the CloudFormation API', async () => {
7+
// GIVEN
8+
const listStackResMock: jest.Mock<AWS.CloudFormation.ListStackResourcesOutput, AWS.CloudFormation.ListStackResourcesInput[]> = jest.fn();
9+
const mockSdk = new MockSdk();
10+
mockSdk.stubCloudFormation({
11+
listStackResources: listStackResMock,
12+
});
13+
listStackResMock.mockReturnValue({
14+
StackResourceSummaries: [],
15+
NextToken: undefined,
16+
});
17+
const res = new LazyListStackResources(mockSdk, 'StackName');
18+
19+
// WHEN
20+
void res.listStackResources();
21+
void res.listStackResources();
22+
void res.listStackResources();
23+
const result = await res.listStackResources();
24+
25+
// THEN
26+
expect(result.length).toBe(0);
27+
expect(listStackResMock).toHaveBeenCalledTimes(1);
28+
});
29+
});

0 commit comments

Comments
 (0)