-
Notifications
You must be signed in to change notification settings - Fork 88
first test on fixing locks for integration tests with lookups #1181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -103,14 +103,21 @@ export class ToolkitLibRunnerEngine implements ICdk { | |
| }); | ||
|
|
||
| try { | ||
| await using lock = await this.toolkit.synth(cx, { | ||
| // Explicitly manage the lock lifecycle instead of using 'await using' | ||
| // to ensure it's properly released before any subsequent synth operations | ||
| const lock = await this.toolkit.synth(cx, { | ||
| validateStacks: false, | ||
| stacks: { | ||
| strategy: StackSelectionStrategy.ALL_STACKS, | ||
| failOnEmpty: false, | ||
| }, | ||
| }); | ||
| await this.validateRegion(lock); | ||
| try { | ||
| await this.validateRegion(lock); | ||
| } finally { | ||
| // Always dispose the lock to avoid conflicts with subsequent synth operations | ||
| await lock.dispose(); | ||
| } | ||
| } catch (e: any) { | ||
| if (e.message.includes('Missing context keys')) { | ||
| // @TODO - silently ignore missing context | ||
|
|
@@ -140,15 +147,26 @@ export class ToolkitLibRunnerEngine implements ICdk { | |
| */ | ||
| public async deploy(options: DeployOptions) { | ||
| const cx = await this.cx(options); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be |
||
| await this.toolkit.deploy(cx, { | ||
| roleArn: options.roleArn, | ||
| traceLogs: options.traceLogs, | ||
| stacks: this.stackSelector(options), | ||
| deploymentMethod: { | ||
| method: 'change-set', | ||
| }, | ||
| outputsFile: options.outputsFile ? path.join(this.options.workingDirectory, options.outputsFile) : undefined, | ||
| }); | ||
| try { | ||
| await this.toolkit.deploy(cx, { | ||
| roleArn: options.roleArn, | ||
| traceLogs: options.traceLogs, | ||
| stacks: this.stackSelector(options), | ||
| deploymentMethod: { | ||
| method: 'change-set', | ||
| }, | ||
| outputsFile: options.outputsFile ? path.join(this.options.workingDirectory, options.outputsFile) : undefined, | ||
| }); | ||
| } finally { | ||
| // Always dispose the cloud assembly source to release any locks | ||
| if (this.isDisposableCloudAssemblySource(cx)) { | ||
| await cx.dispose(); | ||
| } | ||
|
|
||
| if (options.output) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this necessary? |
||
| this.cleanupReadLocks(path.join(this.options.workingDirectory, options.output)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -202,7 +220,7 @@ export class ToolkitLibRunnerEngine implements ICdk { | |
| // check if the app is a path to existing snapshot and then use it as an assembly directory | ||
| const potentialCxPath = path.join(this.options.workingDirectory, options.app); | ||
| if (fs.pathExistsSync(potentialCxPath) && fs.statSync(potentialCxPath).isDirectory()) { | ||
| return this.toolkit.fromAssemblyDirectory(potentialCxPath); | ||
| return this.toolkit.fromAssemblyDirectory(potentialCxPath, { holdLock: false }); | ||
| } | ||
|
|
||
| let outdir; | ||
|
|
@@ -241,6 +259,25 @@ export class ToolkitLibRunnerEngine implements ICdk { | |
| }; | ||
| } | ||
|
|
||
| private cleanupReadLocks(directory: string): void { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like you're saying "the read locks aren't cleaned up properly, so I will do it myself in a couple of additional places". Did we figure out already why they weren't being cleaned up? |
||
| if (!fs.pathExistsSync(directory)) { | ||
| return; | ||
| } | ||
|
|
||
| const prefix = `read.${process.pid}.`; | ||
| for (const entry of fs.readdirSync(directory)) { | ||
| if (entry.startsWith(prefix) && entry.endsWith('.lock')) { | ||
| fs.removeSync(path.join(directory, entry)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private isDisposableCloudAssemblySource( | ||
| source: ICloudAssemblySource, | ||
| ): source is ICloudAssemblySource & { dispose: () => Promise<void> } { | ||
| return typeof (source as { dispose?: unknown }).dispose === 'function'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be called |
||
| } | ||
|
|
||
| /** | ||
| * Check that the regions for the stacks in the CloudAssembly match the regions requested on the engine | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,13 @@ export interface AssemblyDirectoryProps { | |
| * @default true | ||
| */ | ||
| readonly failOnMissingContext?: boolean; | ||
|
|
||
| /** | ||
| * Whether to hold a read lock for the lifetime of the produced assembly. | ||
| * | ||
| * @default true | ||
| */ | ||
| readonly holdLock?: boolean; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -410,6 +417,25 @@ export abstract class CloudAssemblySourceBuilder { | |
| const readLock = await new RWLock(directory).acquireRead(); | ||
| try { | ||
| const asm = await assemblyFromDirectory(directory, services.ioHelper, props.loadAssemblyOptions); | ||
| if (props.holdLock === false) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we not want a read lock? It's there for a reason. |
||
| await readLock.release(); | ||
| const noOpLock = { release: async () => undefined }; | ||
| const assembly = new ReadableCloudAssembly(asm, noOpLock, { deleteOnDispose: false }); | ||
| if (assembly.cloudAssembly.manifest.missing && assembly.cloudAssembly.manifest.missing.length > 0) { | ||
| if (props.failOnMissingContext ?? true) { | ||
| const missingKeysSet = missingContextKeys(assembly.cloudAssembly.manifest.missing); | ||
| const missingKeys = Array.from(missingKeysSet); | ||
| throw AssemblyError.withCause( | ||
| 'Assembly contains missing context. ' + | ||
| "Make sure all necessary context is already in 'cdk.context.json' by running 'cdk synth' on a machine with sufficient AWS credentials and committing the result. " + | ||
| `Missing context keys: '${missingKeys.join(', ')}'`, | ||
| 'Error producing assembly', | ||
| ); | ||
| } | ||
| } | ||
| return new CachedCloudAssembly(assembly); | ||
| } | ||
|
|
||
| const assembly = new ReadableCloudAssembly(asm, readLock, { deleteOnDispose: false }); | ||
| if (assembly.cloudAssembly.manifest.missing && assembly.cloudAssembly.manifest.missing.length > 0) { | ||
| if (props.failOnMissingContext ?? true) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this change.
It seems to me that the new code should do the same thing as the old code. What does this change?