Skip to content

Commit b6ad97f

Browse files
authored
fix(cli): release outdir lock when synth fails (#30874)
### Issue # (if applicable) Closes #27864 ### Reason for this change When using cdk watch mode, a synth failure causes the CDK CLI to no longer deploy changes. The CDK CLI must be restarted to resume watch mode. The cause of the issue is that CDK CLI never releases the outdir write lock if synthing fails, so subsequent attempts to exec the user's app cannot acquire the outdir writer lock. ### Description of changes I added a try/catch that releases the outdir writer lock & rethrows the error when a synth fails. ### Description of how you validated changes I added a unit test. I also ran the modified cdk cli on a project of my own and simulated the failure of a synth to see whether the issue was resolved, and it is. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent e220e90 commit b6ad97f

File tree

2 files changed

+46
-21
lines changed

2 files changed

+46
-21
lines changed

packages/aws-cdk/lib/api/cxapp/exec.ts

+26-21
Original file line numberDiff line numberDiff line change
@@ -58,37 +58,42 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom
5858
debug('outdir:', outdir);
5959
env[cxapi.OUTDIR_ENV] = outdir;
6060

61-
// Acquire a read lock on the output directory
61+
// Acquire a lock on the output directory
6262
const writerLock = await new RWLock(outdir).acquireWrite();
6363

64-
// Send version information
65-
env[cxapi.CLI_ASM_VERSION_ENV] = cxschema.Manifest.version();
66-
env[cxapi.CLI_VERSION_ENV] = versionNumber();
64+
try {
65+
// Send version information
66+
env[cxapi.CLI_ASM_VERSION_ENV] = cxschema.Manifest.version();
67+
env[cxapi.CLI_VERSION_ENV] = versionNumber();
6768

68-
debug('env:', env);
69+
debug('env:', env);
6970

70-
const envVariableSizeLimit = os.platform() === 'win32' ? 32760 : 131072;
71-
const [smallContext, overflow] = splitBySize(context, spaceAvailableForContext(env, envVariableSizeLimit));
71+
const envVariableSizeLimit = os.platform() === 'win32' ? 32760 : 131072;
72+
const [smallContext, overflow] = splitBySize(context, spaceAvailableForContext(env, envVariableSizeLimit));
7273

73-
// Store the safe part in the environment variable
74-
env[cxapi.CONTEXT_ENV] = JSON.stringify(smallContext);
74+
// Store the safe part in the environment variable
75+
env[cxapi.CONTEXT_ENV] = JSON.stringify(smallContext);
7576

76-
// If there was any overflow, write it to a temporary file
77-
let contextOverflowLocation;
78-
if (Object.keys(overflow ?? {}).length > 0) {
79-
const contextDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-context'));
80-
contextOverflowLocation = path.join(contextDir, 'context-overflow.json');
81-
fs.writeJSONSync(contextOverflowLocation, overflow);
82-
env[cxapi.CONTEXT_OVERFLOW_LOCATION_ENV] = contextOverflowLocation;
83-
}
77+
// If there was any overflow, write it to a temporary file
78+
let contextOverflowLocation;
79+
if (Object.keys(overflow ?? {}).length > 0) {
80+
const contextDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-context'));
81+
contextOverflowLocation = path.join(contextDir, 'context-overflow.json');
82+
fs.writeJSONSync(contextOverflowLocation, overflow);
83+
env[cxapi.CONTEXT_OVERFLOW_LOCATION_ENV] = contextOverflowLocation;
84+
}
8485

85-
await exec(commandLine.join(' '));
86+
await exec(commandLine.join(' '));
8687

87-
const assembly = createAssembly(outdir);
88+
const assembly = createAssembly(outdir);
8889

89-
contextOverflowCleanup(contextOverflowLocation, assembly);
90+
contextOverflowCleanup(contextOverflowLocation, assembly);
9091

91-
return { assembly, lock: await writerLock.convertToReaderLock() };
92+
return { assembly, lock: await writerLock.convertToReaderLock() };
93+
} catch (e) {
94+
await writerLock.release();
95+
throw e;
96+
}
9297

9398
async function exec(commandAndArgs: string) {
9499
return new Promise<void>((ok, fail) => {

packages/aws-cdk/test/api/exec.test.ts

+20
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Configuration } from '../../lib/settings';
1212
import { testAssembly } from '../util';
1313
import { mockSpawn } from '../util/mock-child_process';
1414
import { MockSdkProvider } from '../util/mock-sdk';
15+
import { RWLock } from '../../lib/api/util/rwlock';
1516

1617
let sdkProvider: MockSdkProvider;
1718
let config: Configuration;
@@ -234,6 +235,25 @@ test('cli does not throw when the `build` script succeeds', async () => {
234235
await lock.release();
235236
}, TEN_SECOND_TIMEOUT);
236237

238+
test('cli releases the outdir lock when execProgram throws', async () => {
239+
// GIVEN
240+
config.settings.set(['app'], 'cloud-executable');
241+
mockSpawn({
242+
commandLine: 'fake-command',
243+
exitCode: 127,
244+
});
245+
246+
// WHEN
247+
await expect(execProgram(sdkProvider, config)).rejects.toThrow();
248+
249+
const output = config.settings.get(['output']);
250+
expect(output).toBeDefined();
251+
252+
// check that the lock is released
253+
const lock = await new RWLock(output).acquireWrite();
254+
await lock.release();
255+
});
256+
237257
function writeOutputAssembly() {
238258
const asm = testAssembly({
239259
stacks: [],

0 commit comments

Comments
 (0)