diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index 4069d7a660143..31f2fca029dd9 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -58,37 +58,42 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom debug('outdir:', outdir); env[cxapi.OUTDIR_ENV] = outdir; - // Acquire a read lock on the output directory + // Acquire a lock on the output directory const writerLock = await new RWLock(outdir).acquireWrite(); - // Send version information - env[cxapi.CLI_ASM_VERSION_ENV] = cxschema.Manifest.version(); - env[cxapi.CLI_VERSION_ENV] = versionNumber(); + try { + // Send version information + env[cxapi.CLI_ASM_VERSION_ENV] = cxschema.Manifest.version(); + env[cxapi.CLI_VERSION_ENV] = versionNumber(); - debug('env:', env); + debug('env:', env); - const envVariableSizeLimit = os.platform() === 'win32' ? 32760 : 131072; - const [smallContext, overflow] = splitBySize(context, spaceAvailableForContext(env, envVariableSizeLimit)); + const envVariableSizeLimit = os.platform() === 'win32' ? 32760 : 131072; + const [smallContext, overflow] = splitBySize(context, spaceAvailableForContext(env, envVariableSizeLimit)); - // Store the safe part in the environment variable - env[cxapi.CONTEXT_ENV] = JSON.stringify(smallContext); + // Store the safe part in the environment variable + env[cxapi.CONTEXT_ENV] = JSON.stringify(smallContext); - // If there was any overflow, write it to a temporary file - let contextOverflowLocation; - if (Object.keys(overflow ?? {}).length > 0) { - const contextDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-context')); - contextOverflowLocation = path.join(contextDir, 'context-overflow.json'); - fs.writeJSONSync(contextOverflowLocation, overflow); - env[cxapi.CONTEXT_OVERFLOW_LOCATION_ENV] = contextOverflowLocation; - } + // If there was any overflow, write it to a temporary file + let contextOverflowLocation; + if (Object.keys(overflow ?? {}).length > 0) { + const contextDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-context')); + contextOverflowLocation = path.join(contextDir, 'context-overflow.json'); + fs.writeJSONSync(contextOverflowLocation, overflow); + env[cxapi.CONTEXT_OVERFLOW_LOCATION_ENV] = contextOverflowLocation; + } - await exec(commandLine.join(' ')); + await exec(commandLine.join(' ')); - const assembly = createAssembly(outdir); + const assembly = createAssembly(outdir); - contextOverflowCleanup(contextOverflowLocation, assembly); + contextOverflowCleanup(contextOverflowLocation, assembly); - return { assembly, lock: await writerLock.convertToReaderLock() }; + return { assembly, lock: await writerLock.convertToReaderLock() }; + } catch (e) { + await writerLock.release(); + throw e; + } async function exec(commandAndArgs: string) { return new Promise((ok, fail) => { diff --git a/packages/aws-cdk/test/api/exec.test.ts b/packages/aws-cdk/test/api/exec.test.ts index a8c32aed06d8a..f3061678e3d77 100644 --- a/packages/aws-cdk/test/api/exec.test.ts +++ b/packages/aws-cdk/test/api/exec.test.ts @@ -12,6 +12,7 @@ import { Configuration } from '../../lib/settings'; import { testAssembly } from '../util'; import { mockSpawn } from '../util/mock-child_process'; import { MockSdkProvider } from '../util/mock-sdk'; +import { RWLock } from '../../lib/api/util/rwlock'; let sdkProvider: MockSdkProvider; let config: Configuration; @@ -234,6 +235,25 @@ test('cli does not throw when the `build` script succeeds', async () => { await lock.release(); }, TEN_SECOND_TIMEOUT); +test('cli releases the outdir lock when execProgram throws', async () => { + // GIVEN + config.settings.set(['app'], 'cloud-executable'); + mockSpawn({ + commandLine: 'fake-command', + exitCode: 127, + }); + + // WHEN + await expect(execProgram(sdkProvider, config)).rejects.toThrow(); + + const output = config.settings.get(['output']); + expect(output).toBeDefined(); + + // check that the lock is released + const lock = await new RWLock(output).acquireWrite(); + await lock.release(); +}); + function writeOutputAssembly() { const asm = testAssembly({ stacks: [],