From b61463527ff53525ef7c00ecb4445df4866569f9 Mon Sep 17 00:00:00 2001 From: Rico Huijbers <rix0rrr@gmail.com> Date: Fri, 2 Jun 2023 10:29:03 +0200 Subject: [PATCH 1/3] chore(cli): print detected cycle if work graph stalls To help with diagnosing #25806, if the work graph can't make any progress anymore because of a dependency cycle, print the cycle that was found instead of all the remaining nodes. --- .../aws-custom-resource/sdk-api-metadata.json | 11 ------- packages/aws-cdk/lib/util/work-graph.ts | 29 +++++++++++++++++-- packages/aws-cdk/test/work-graph.test.ts | 7 +++-- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/sdk-api-metadata.json b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/sdk-api-metadata.json index 83d53e4eb03bc..c1f19ce849b8e 100644 --- a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/sdk-api-metadata.json +++ b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/sdk-api-metadata.json @@ -1284,16 +1284,5 @@ }, "internetmonitor": { "name": "InternetMonitor" - }, - "ivsrealtime": { - "prefix": "ivs-realtime", - "name": "IVSRealTime" - }, - "vpclattice": { - "prefix": "vpc-lattice", - "name": "VPCLattice" - }, - "osis": { - "name": "OSIS" } } \ No newline at end of file diff --git a/packages/aws-cdk/lib/util/work-graph.ts b/packages/aws-cdk/lib/util/work-graph.ts index c51a9a2b3f4e8..d35eaf8b9d166 100644 --- a/packages/aws-cdk/lib/util/work-graph.ts +++ b/packages/aws-cdk/lib/util/work-graph.ts @@ -272,7 +272,7 @@ export class WorkGraph { this.readyPool.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)); if (this.readyPool.length === 0 && activeCount === 0 && pendingCount > 0) { - throw new Error(`Unable to make progress anymore among: ${this}`); + throw new Error(`Unable to make progress anymore, dependency cycle between remaining artifacts: ${this.findCycle().join(' -> ')}`); } } @@ -283,6 +283,31 @@ export class WorkGraph { } } } + + /** + * Find cycles in a graph + * + * Not the fastest, but effective and should be rare + */ + private findCycle(): string[] { + const self = this; + for (const nodeId of Object.keys(this.nodes)) { + const cycle = recurse(nodeId, [nodeId]); + if (cycle) { return cycle; } + } + return ['No cycle found!']; + + function recurse(nodeId: string, path: string[]): string[] | undefined { + for (const dep of self.nodes[nodeId].dependencies ?? []) { + if (dep === path[0]) { return [...path, dep]; } + + const cycle = recurse(dep, [...path, dep]); + if (cycle) { return cycle; } + } + + return undefined; + } + } } export interface WorkGraphActions { @@ -297,4 +322,4 @@ function sum(xs: number[]) { ret += x; } return ret; -} \ No newline at end of file +} diff --git a/packages/aws-cdk/test/work-graph.test.ts b/packages/aws-cdk/test/work-graph.test.ts index 5b7a2d1235ec8..8486d667e991f 100644 --- a/packages/aws-cdk/test/work-graph.test.ts +++ b/packages/aws-cdk/test/work-graph.test.ts @@ -328,6 +328,7 @@ describe('WorkGraph', () => { toDeploy: createArtifacts([ { id: 'A', type: 'stack', stackDependencies: ['A'] }, ]), + expectedError: 'A -> A', }, { scenario: 'A -> B, B -> A', @@ -335,6 +336,7 @@ describe('WorkGraph', () => { { id: 'A', type: 'stack', stackDependencies: ['B'] }, { id: 'B', type: 'stack', stackDependencies: ['A'] }, ]), + expectedError: 'A -> B -> A', }, { scenario: 'A, B -> C, C -> D, D -> B', @@ -344,12 +346,13 @@ describe('WorkGraph', () => { { id: 'C', type: 'stack', stackDependencies: ['D'] }, { id: 'D', type: 'stack', stackDependencies: ['B'] }, ]), + expectedError: 'B -> C -> D -> B', }, - ])('Failure - Graph Circular Dependencies - $scenario', async ({ toDeploy }) => { + ])('Failure - Graph Circular Dependencies - $scenario', async ({ toDeploy, expectedError }) => { const graph = new WorkGraph(); addTestArtifactsToGraph(toDeploy, graph); - await expect(graph.doParallel(1, callbacks)).rejects.toThrowError(/Unable to make progress anymore/); + await expect(graph.doParallel(1, callbacks)).rejects.toThrowError(new RegExp(`Unable to make progress.*${expectedError}`)); }); }); From 70f254c29f8b37c9c6adc1acaa14195c4c6569db Mon Sep 17 00:00:00 2001 From: Rico Huijbers <rix0rrr@gmail.com> Date: Fri, 2 Jun 2023 13:05:57 +0200 Subject: [PATCH 2/3] Faster cycle finding --- packages/aws-cdk/lib/util/work-graph.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/aws-cdk/lib/util/work-graph.ts b/packages/aws-cdk/lib/util/work-graph.ts index d35eaf8b9d166..4d565ade4be3b 100644 --- a/packages/aws-cdk/lib/util/work-graph.ts +++ b/packages/aws-cdk/lib/util/work-graph.ts @@ -290,6 +290,7 @@ export class WorkGraph { * Not the fastest, but effective and should be rare */ private findCycle(): string[] { + const seen = new Set<string>(); const self = this; for (const nodeId of Object.keys(this.nodes)) { const cycle = recurse(nodeId, [nodeId]); @@ -298,14 +299,24 @@ export class WorkGraph { return ['No cycle found!']; function recurse(nodeId: string, path: string[]): string[] | undefined { - for (const dep of self.nodes[nodeId].dependencies ?? []) { - if (dep === path[0]) { return [...path, dep]; } - - const cycle = recurse(dep, [...path, dep]); - if (cycle) { return cycle; } + if (seen.has(nodeId)) { + return undefined; } + try { + for (const dep of self.nodes[nodeId].dependencies ?? []) { + const index = path.indexOf(dep); + if (index > -1) { + return [...path.slice(index), dep]; + } - return undefined; + const cycle = recurse(dep, [...path, dep]); + if (cycle) { return cycle; } + } + + return undefined; + } finally { + seen.add(nodeId); + } } } } From 880aa27855fa9d63d5f6c11780d4af1c63171e8c Mon Sep 17 00:00:00 2001 From: Rico Huijbers <rix0rrr@gmail.com> Date: Fri, 2 Jun 2023 15:48:37 +0200 Subject: [PATCH 3/3] Undo changes to SDK-API-METADATA --- .../lib/aws-custom-resource/sdk-api-metadata.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/sdk-api-metadata.json b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/sdk-api-metadata.json index c1f19ce849b8e..83d53e4eb03bc 100644 --- a/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/sdk-api-metadata.json +++ b/packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/sdk-api-metadata.json @@ -1284,5 +1284,16 @@ }, "internetmonitor": { "name": "InternetMonitor" + }, + "ivsrealtime": { + "prefix": "ivs-realtime", + "name": "IVSRealTime" + }, + "vpclattice": { + "prefix": "vpc-lattice", + "name": "VPCLattice" + }, + "osis": { + "name": "OSIS" } } \ No newline at end of file