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