Skip to content

Commit 88b599d

Browse files
authored
fix(aws-cdk): continue after exceptions in stack monitor (#791)
If exceptions occur in the stack monitor, a new invocation wouldn't be scheduled. We're now catching the exception and continuing. This will not fix #787, but it will reduce the frequency and impact of it occurring. Also fixes a race condition where the stack monitor might print more lines after it has already been stopped and the CDK final output has already been printed.
1 parent 4980c97 commit 88b599d

File tree

2 files changed

+28
-6
lines changed

2 files changed

+28
-6
lines changed

Diff for: packages/aws-cdk/lib/api/deploy-stack.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export async function deployStack(stack: cxapi.SynthesizedStack,
6868
const monitor = quiet ? undefined : new StackActivityMonitor(cfn, deployName, stack.metadata, changeSetDescription.Changes.length).start();
6969
debug('Execution of changeset %s on stack %s has started; waiting for the update to complete...', changeSetName, deployName);
7070
await waitForStack(cfn, deployName);
71-
if (monitor) { monitor.stop(); }
71+
if (monitor) { await monitor.stop(); }
7272
debug('Stack %s has completed updating', deployName);
7373
return { noOp: false, outputs: await getStackOutputs(cfn, deployName), stackArn: changeSet.StackId! };
7474
}
@@ -127,7 +127,7 @@ export async function destroyStack(stack: cxapi.StackInfo, sdk: SDK, deployName?
127127
const monitor = quiet ? undefined : new StackActivityMonitor(cfn, deployName).start();
128128
await cfn.deleteStack({ StackName: deployName }).promise().catch(e => { throw e; });
129129
const destroyedStack = await waitForStack(cfn, deployName, false);
130-
if (monitor) { monitor.stop(); }
130+
if (monitor) { await monitor.stop(); }
131131
if (destroyedStack && destroyedStack.StackStatus !== 'DELETE_COMPLETE') {
132132
const status = StackStatus.fromStackDescription(destroyedStack);
133133
throw new Error(`Failed to destroy ${deployName}: ${status}`);

Diff for: packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts

+26-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import cxapi = require('@aws-cdk/cx-api');
22
import aws = require('aws-sdk');
33
import colors = require('colors/safe');
44
import util = require('util');
5+
import { error } from '../../../logging';
56

67
interface StackActivity {
78
readonly event: aws.CloudFormation.StackEvent;
@@ -59,6 +60,11 @@ export class StackActivityMonitor {
5960
*/
6061
private lastPrintTime = Date.now();
6162

63+
/**
64+
* Set to the activity of reading the current events
65+
*/
66+
private readPromise?: Promise<AWS.CloudFormation.StackEvent[]>;
67+
6268
constructor(private readonly cfn: aws.CloudFormation,
6369
private readonly stackName: string,
6470
private readonly metadata?: cxapi.StackMetadata,
@@ -80,11 +86,17 @@ export class StackActivityMonitor {
8086
return this;
8187
}
8288

83-
public stop() {
89+
public async stop() {
8490
this.active = false;
8591
if (this.tickTimer) {
8692
clearTimeout(this.tickTimer);
8793
}
94+
95+
if (this.readPromise) {
96+
// We're currently reading events, wait for it to finish and print them before continuing.
97+
await this.readPromise;
98+
this.flushEvents();
99+
}
88100
}
89101

90102
private scheduleNextTick() {
@@ -99,8 +111,18 @@ export class StackActivityMonitor {
99111
return;
100112
}
101113

102-
await this.readEvents();
103-
this.flushEvents();
114+
try {
115+
this.readPromise = this.readEvents();
116+
await this.readPromise;
117+
this.readPromise = undefined;
118+
119+
// We might have been stop()ped while the network call was in progress.
120+
if (!this.active) { return; }
121+
122+
this.flushEvents();
123+
} catch (e) {
124+
error("Error occurred while monitoring stack: %s", e);
125+
}
104126
this.scheduleNextTick();
105127
}
106128

@@ -220,7 +242,7 @@ export class StackActivityMonitor {
220242
return colors.reset;
221243
}
222244

223-
private async readEvents(nextToken?: string) {
245+
private async readEvents(nextToken?: string): Promise<AWS.CloudFormation.StackEvent[]> {
224246
const output = await this.cfn.describeStackEvents({ StackName: this.stackName, NextToken: nextToken }).promise()
225247
.catch( e => {
226248
if (e.code === 'ValidationError' && e.message === `Stack [${this.stackName}] does not exist`) {

0 commit comments

Comments
 (0)