From ac26a1c8bfd1cd25831a60d9d82cbbe831ff63e6 Mon Sep 17 00:00:00 2001 From: Michel Versluijs Date: Fri, 10 Apr 2026 14:01:23 +0200 Subject: [PATCH] Fix publisher deadlock when workspace child resources are published in commit mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When running with COMMIT_ID set and FeatureManagement__Workspaces=true, the publisher would hang silently after 'Running publisher...' with no APIM calls and no error output. Root cause: processDelete cascaded to successors for every resource where isInFileSystem=false, not just resources actually being deleted. WorkspaceResource has no artifact file and is never part of the resource set, so it always entered processDelete. Its cascade logic then awaited the workspace child tasks (e.g. WorkspaceApiResource), which were themselves waiting for the workspace task via the predecessor relationship — a circular wait. Fix: add a guard at the top of processDelete that returns immediately when the resource is not in the resource set. The existing conditional around deleteResource is removed as it is now unreachable when false. Fixes #838 --- src/publisher/Publisher.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/publisher/Publisher.cs b/src/publisher/Publisher.cs index 5883331d..432e9468 100644 --- a/src/publisher/Publisher.cs +++ b/src/publisher/Publisher.cs @@ -98,6 +98,13 @@ await predecessors.IterTaskParallel(async predecessor => await processResource(p async ValueTask processDelete() { + // Skip if not actually being deleted this run (e.g. WorkspaceResource has no artifact file). + // Cascading to successors here would deadlock with their processPut tasks. + if (resourceSet.Contains(resourceKey) is false) + { + return; + } + // Process successors first var successors = previousRelationships.Successors .Find(resourceKey) @@ -108,10 +115,7 @@ await successors.IterTaskParallel(async successor => await processResource(succe cancellationToken); // Delete resource - if (resourceSet.Contains(resourceKey)) - { - await deleteResource(resourceKey, cancellationToken); - } + await deleteResource(resourceKey, cancellationToken); } } }