From 8723031e93862ece0c22b16784952825708df13c Mon Sep 17 00:00:00 2001 From: ahmed Date: Fri, 5 Jun 2026 17:12:17 +0300 Subject: [PATCH] Deadlock test --- .../Esiur/Protocol/EpConnectionProtocol.cs | 38 +++++++++++++++++-- .../Integration/DeadlockIntegrationTests.cs | 5 ++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Libraries/Esiur/Protocol/EpConnectionProtocol.cs b/Libraries/Esiur/Protocol/EpConnectionProtocol.cs index c2fcf8d..0e894af 100644 --- a/Libraries/Esiur/Protocol/EpConnectionProtocol.cs +++ b/Libraries/Esiur/Protocol/EpConnectionProtocol.cs @@ -63,6 +63,8 @@ partial class EpConnection // (e.g. two concurrent fetches A<->B) so a placeholder can break the deadlock, while // independent/app-facing fetches of an in-flight resource simply wait for full attachment. readonly Dictionary> _fetchBlockedOn = new Dictionary>(); + readonly object _deliveredRootsLock = new object(); + readonly Dictionary> _deliveredRoots = new Dictionary>(); /// /// Strategy FetchResource uses for an in-flight resource. Defaults to the new wait + cycle @@ -1990,10 +1992,10 @@ partial class EpConnection req.Then(result => { - // The resource is being handed to the application: publish its fully-attached - // graph so that, if any dependency is only partially attached, it stays unpublished. + // The resource is being handed to the application: remember it and publish its graph + // once all reachable dependencies have attached. if (result is EpResource resource) - PublishGraph(resource); + TrackDeliveredRoot(resource); rt.Trigger(result); }).Error(ex => rt.TriggerError(ex)); @@ -2134,7 +2136,7 @@ partial class EpConnection reachable.Add(node); - if (node.Status != ResourceStatus.Attached) + if (node.Status != ResourceStatus.Attached && node.Status != ResourceStatus.Published) { fullyAttached = false; continue; // do not traverse into a not-yet-attached node @@ -2149,6 +2151,32 @@ partial class EpConnection node.Publish(); } + void TrackDeliveredRoot(EpResource root) + { + lock (_deliveredRootsLock) + _deliveredRoots[root.ResourceInstanceId] = new WeakReference(root); + + TryPublishDeliveredRoots(); + } + + void TryPublishDeliveredRoots() + { + lock (_deliveredRootsLock) + { + var stale = new List(); + foreach (var pair in _deliveredRoots) + { + if (pair.Value.TryGetTarget(out var root)) + PublishGraph(root); + else + stale.Add(pair.Key); + } + + foreach (var key in stale) + _deliveredRoots.Remove(key); + } + } + public AsyncReply FetchResource(uint id, uint[] requestSequence) { //lock (fetchLock) @@ -2288,6 +2316,7 @@ partial class EpConnection _attachedResources[id] = new WeakReference(dr); // attached: no longer part of the in-flight wait-for graph. ClearFetchNode(id); + TryPublishDeliveredRoots(); reply.Trigger(dr); }).Error(ex => { _resourceRequests.Remove(id); ClearFetchNode(id); reply.TriggerError(ex); }); }; @@ -2418,6 +2447,7 @@ partial class EpConnection _neededResources.Remove(id); _attachedResources[id] = new WeakReference(resource); ClearFetchNode(id); + TryPublishDeliveredRoots(); reply.Trigger(resource); }) .Error(ex => { _resourceRequests.Remove(id); ClearFetchNode(id); reply.TriggerError(ex); }); diff --git a/Tests/Unit/Integration/DeadlockIntegrationTests.cs b/Tests/Unit/Integration/DeadlockIntegrationTests.cs index ab54473..488da6b 100644 --- a/Tests/Unit/Integration/DeadlockIntegrationTests.cs +++ b/Tests/Unit/Integration/DeadlockIntegrationTests.cs @@ -139,7 +139,7 @@ public class DeadlockIntegrationTests if (node == null || !seen.Add(node.ResourceInstanceId)) continue; - if (node.Status != Resource.ResourceStatus.Attached) + if (node.Status != Resource.ResourceStatus.Attached && node.Status != Resource.ResourceStatus.Published) { allAttached = false; continue; // do not traverse into a partially attached node @@ -293,7 +293,8 @@ public class DeadlockIntegrationTests if (node.Status != ResourceStatus.Published) unpublished++; - if ((node.Status == ResourceStatus.Attached) && node.TryGetPropertyValue((byte)1, out var linksObj) && linksObj is IEnumerable links) + if ((node.Status == ResourceStatus.Attached || node.Status == ResourceStatus.Published) + && node.TryGetPropertyValue((byte)1, out var linksObj) && linksObj is IEnumerable links) foreach (var child in links) if (child is EpResource childResource) queue.Enqueue(childResource);