Context
TaskHost tracks yield state via _yieldThreadId (TaskHost.cs:106), and Yield() verifies you are not already yielded (TaskHost.cs:361), but there is no enforcement after Execute() returns. If a third-party task calls IBuildEngine3.Yield() and returns from Execute() without calling Reacquire():
RequestBuilder.Yield() sets _blockType = Yielded and tells the engine the entry is Waiting
- The task returns normally - the builder thread never blocks (it never reaches the
WaitHandle.WaitAny in Reacquire())
- The engine may activate another builder since the entry appears to be
Waiting
- The original builder thread continues through the rest of
TargetBuilder -> BuildTargets returns
This is a protocol violation by the task, but the engine should defend against it.
Proposal
After taskExecutionHost.Execute() returns in TaskBuilder.ExecuteInstantiatedTask (TaskBuilder.cs:832), check whether the task left the node in a yielded state (via TaskHost._yieldThreadId != -1) and if so, force a Reacquire() before continuing. This would:
- Restore the engine one-active-builder invariant
- Log a warning so task authors can fix their code
- Prevent any downstream issues from the inconsistent state
Affected code
TaskBuilder.cs:832 - after taskResult = taskExecutionHost.Execute()
TaskHost.cs:106 - _yieldThreadId field (currently private, would need to be exposed or checked via a property)
Why this matters
The engine scheduling correctness depends on the invariant that only one builder is active per node at a time. A yield-without-reacquire silently breaks this invariant. Currently the only defense is that all built-in tasks use try/finally around Yield()/Reacquire() (ToolTask.cs:957-1027, Unzip.cs:104-166, ZipDirectory.cs:78-146), but third-party tasks have no such guarantee.
Context
TaskHosttracks yield state via_yieldThreadId(TaskHost.cs:106), andYield()verifies you are not already yielded (TaskHost.cs:361), but there is no enforcement afterExecute()returns. If a third-party task callsIBuildEngine3.Yield()and returns fromExecute()without callingReacquire():RequestBuilder.Yield()sets_blockType = Yieldedand tells the engine the entry isWaitingWaitHandle.WaitAnyinReacquire())WaitingTargetBuilder->BuildTargetsreturnsThis is a protocol violation by the task, but the engine should defend against it.
Proposal
After
taskExecutionHost.Execute()returns inTaskBuilder.ExecuteInstantiatedTask(TaskBuilder.cs:832), check whether the task left the node in a yielded state (viaTaskHost._yieldThreadId != -1) and if so, force aReacquire()before continuing. This would:Affected code
TaskBuilder.cs:832- aftertaskResult = taskExecutionHost.Execute()TaskHost.cs:106-_yieldThreadIdfield (currently private, would need to be exposed or checked via a property)Why this matters
The engine scheduling correctness depends on the invariant that only one builder is active per node at a time. A yield-without-reacquire silently breaks this invariant. Currently the only defense is that all built-in tasks use
try/finallyaroundYield()/Reacquire()(ToolTask.cs:957-1027,Unzip.cs:104-166,ZipDirectory.cs:78-146), but third-party tasks have no such guarantee.