Skip to content

Deny child attachment to the tasks Invoke queues [patch] - #93

Open
matt-edmondson wants to merge 1 commit into
mainfrom
fix/92-deny-child-attach
Open

matt-edmondson wants to merge 1 commit into
mainfrom
fix/92-deny-child-attach

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

The tasks that an off-thread Invoke/InvokeAsync queues were built with RunContinuationsAsynchronously only. A child that the delegate started with AttachedToParent therefore attached to them, which caused three problems:

On the owner thread, the same call runs inline with no parent task, so none of this happens there.

Both queued tasks (the Action and the Func<TReturn> paths, which every overload goes through) are now built with RunContinuationsAsynchronously | DenyChildAttach. This is the same reason Task.Run sets DenyChildAttach.

One more finding while writing the tests: before this fix, the owner's DoInvokes blocked for the child's whole lifetime, not just the caller. A child that waited on something the owner thread does next would deadlock the pump.

Tests (new AttachedChildTaskTests)

  • Invoke(Action) and Invoke(Func<T>) from a worker thread return before a blocking attached child is released. A timer releases the child, not the owner thread, so the old behaviour can't deadlock the test. The children are LongRunning, so the blocked children can't starve the thread pool that the call's continuation needs.
  • Invoke(Action) and InvokeAsync(Func<T>) from a worker don't throw an attached child's exception. The child still faults on its own.
  • With the fix reverted, all four tests fail. With the fix in, the full suite passes (44/44, six consecutive runs).
  • I checked locally that this branch merges cleanly with every other open branch in the repo (Wait for ValueTask-returning delegates in Invoke and InvokeAsync [minor] #87–Document TryBeginInvoke instead of the BeginInvoke the library doesn't have [patch] #90).

Fixes #92


Generated by Claude Code

The task built for an off-thread Invoke or InvokeAsync allowed children, so
an AttachedToParent child started by the delegate attached to it. DoInvokes'
RunSynchronously then blocked the owner's pump until the child finished,
the caller waited too, and a faulted child surfaced as an AggregateException
in place of the delegate's own result. On the owner thread the same call runs
inline with nothing to attach to. The queued tasks now also set
DenyChildAttach, as Task.Run does.

Fixes #92

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW547mxaz1R4r27gAVm
@sonarqubecloud

sonarqubecloud Bot commented Nov 1, 2026

Copy link
Copy Markdown

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.

An off-thread Invoke waits for, and throws from, AttachedToParent child tasks that the same call on the owner thread ignores

2 participants