Skip to content

Fix deferred groups below a nulled object still running and blocking later group members - #184

Open
michaelstaib wants to merge 1 commit into
mst/execution-invalidated-reference-fieldfrom
mst/execution-defer-task-below-nulled-parent
Open

michaelstaib wants to merge 1 commit into
mst/execution-invalidated-reference-fieldfrom
mst/execution-defer-task-below-nulled-parent

Conversation

@michaelstaib

Copy link
Copy Markdown
Owner

A deferred group whose object was nulled before the group ran was still resolved. Its announcement was already suppressed, but its resolvers ran anyway. When such a group held an execution group member, that member never ended its turn, so a later member of the same execution group waited forever.

Changes:

  • A DeferTask checks whether its branch sits below a nulled object, using the same path resolution as the announce check, and skips the group if it does. A branch that was already announced is never skipped.
  • When a group is skipped, ExecutionGroupScope.ReleaseSlots releases the slots of its execution group members, so later members run.

Behavior change: a deferred group whose object was nulled before it runs is no longer resolved, and its execution group members are skipped.

Stacked on #175.

Tests: DeferBelowNulledParentTests cover a nullable ancestor field nulled before the group runs, a parent nulled before the group runs, and a parent nulled after the group resolved. ExecutionGroupTests cover a later member running when a deferred member is below a nulled object, which hung before the fix. Execution.Tests pass on net10.0, and the touched classes pass on net8.0.

Copilot AI balanced review requested due to automatic review settings November 5, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Copilot was unable to run its full agentic suite in this review.

Copilot review overview

2 open issues
What changed in this PR

Adds safeguards to deferred execution when a deferred branch’s parent is nulled, ensuring later execution-group members still run and no deferred payload is announced.

Changes:

  • Add/extend tests covering @defer behavior when ancestors are nulled before/after deferred groups run.
  • Track the ResultDocument for deferred branches and discard branches whose target object becomes null before delivery.
  • Introduce execution-group slot releasing for skipped/dead deferred groups so subsequent members can proceed.
File Description
src/​HotChocolate/​Core/​test/​Execution.Tests/​ExecutionGroups/​ExecutionGroupTests.cs Adds a regression test ensuring “later members” still execute when a deferred member is under a nulled object.
src/​HotChocolate/​Core/​test/​Execution.Tests/​DeferBelowNulledParentTests.cs New focused tests for discarding/avoiding deferred group announcement when ancestors null out.
src/​HotChocolate/​Core/​src/​Types/​Text/​Json/​ResultElement.cs Exposes the owning ResultDocument needed to determine nulling state for deferred branches.
src/​HotChocolate/​Core/​src/​Types/​Execution/​Processing/​Tasks/​ResolverTaskFactory.cs Passes ResultDocument into branch registration so null-detection can work.
src/​HotChocolate/​Core/​src/​Types/​Execution/​Processing/​Tasks/​DeferTask.cs Discards deferred branches that will never be delivered and releases group slots so later tasks can start.
src/​HotChocolate/​Core/​src/​Types/​Execution/​Processing/​ExecutionGroups/​ExecutionGroupScope.cs Adds an API to release a selection’s slot plus descendants (used when skipping deferred group members).
src/​HotChocolate/​Core/​src/​Types/​Execution/​Processing/​DeferExecutionCoordinator.cs Extends branch registration with document tracking and adds TryDiscardNulledBranch for early discard.

Review effort: Lite

Comment on lines +39 to +40
internal ResultDocument Document => _parent;

Comment on lines +89 to +102
/// <summary>
/// Unregisters the deferred branch <paramref name="branchId"/> if its object was nulled before its parent
/// was delivered, returning <c>true</c> if the branch will never be delivered.
/// </summary>
public bool TryDiscardNulledBranch(int branchId)
{
AssertInitialized();

lock (_sync)
{
if (!_branchLookup.TryGetValue(branchId, out var branch))
{
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants