Fix race condition: set _canceled before SignalCore in ProcessWaitState - #127312

Merged
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure
Apr 23, 2026
Merged

Fix race condition: set _canceled before SignalCore in ProcessWaitState#127312
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure

Conversation

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Description

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation fails intermittently in CI because _canceled is set after SignalCore(PosixSignal.SIGKILL) returns. The reaping thread triggered by the kill signal can enter ChildReaped and read _canceled == false before the cancellation/timeout path writes true, producing exitStatus.Canceled == false.

Changes

  • Set _canceled = truebefore calling SignalCore(PosixSignal.SIGKILL) at all call sites (WaitForExitOrKillOnCancellationAsync, WaitForExitOrKillOnTimeoutCore on both Unix and Windows), so the reaping thread always observes the canceled state
  • Remove volatile from _canceled since the _gate lock acquire in ChildReaped provides the necessary memory barrier on the read side

@tmds

tmds commented Apr 23, 2026

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:

waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

The test ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation
was failing intermittently because _canceled was being set outside the _gate lock,
while ChildReaped reads it under the lock to build ProcessExitStatus. This caused
a race where ChildReaped could read _canceled as false before the cancellation
callback set it to true.
Changes:
- Add Cancel() method to SafeProcessHandle (platform-specific implementations)
- On Unix, Cancel() delegates to ProcessWaitState.Cancel() which acquires _gate
before setting _canceled, ensuring atomicity with ChildReaped
- On Windows, Cancel() wraps the existing SignalCore + Canceled property set
- Remove volatile from _canceled field since it's now protected by the lock
- Update all call sites to use Cancel() instead of directly setting Canceled
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d23310ac-167d-4a45-a389-45918504a964
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 08:23
CopilotAI changed the title [WIP] Fix ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync test failure in CIFix race condition: set _canceled under _gate lock in ProcessWaitStateApr 23, 2026
CopilotAI requested a review from adamsitnikApril 23, 2026 08:25
@adamsitnik
adamsitnik marked this pull request as ready for review April 23, 2026 08:45
CopilotAI review requested due to automatic review settings April 23, 2026 08:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an intermittent CI failure by ensuring the “canceled” state is set atomically with process reaping on Unix, eliminating a race between the cancellation/timeout path and ChildReaped exit-status construction.

Changes:

  • Add a Unix ProcessWaitState.Cancel(SafeProcessHandle) that takes _gate before signaling and setting _canceled.
  • Introduce platform-specific SafeProcessHandle.Cancel() helpers and route cancellation/timeout call sites through them (or through the wait state on Unix).
  • Remove volatile from _canceled now that access is lock-protected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.csAdds lock-protected cancel path and removes volatile on _canceled to make cancellation state consistent with ChildReaped.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csSwitches cancellation callback to call the new Cancel() helper.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csAdds Cancel() helper and uses it for timeout kill path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csAdds Cancel() helper that routes through ProcessWaitState.Cancel, and updates timeout kill path.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22fb6e3e-d0e3-4cf8-91b3-5b227dbbc8a4
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@adamsitnik

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:


waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@copilot revert all the changes and implement this suggestion.

CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 11:21

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc7251: set _canceled = true before SignalCore(PosixSignal.SIGKILL) at all call sites (Unix timeout, Unix cancellation, Windows timeout, shared cancellation callback), removed volatile from _canceled. No lock needed. All 561 tests pass.

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc72519. At all 3 call sites, _canceled is now set to true before SignalCore(PosixSignal.SIGKILL), and volatile is removed. All 561 tests pass.

CopilotAI changed the title Fix race condition: set _canceled under _gate lock in ProcessWaitStateFix race condition: set _canceled before SignalCore in ProcessWaitStateApr 23, 2026
@adamsitnik

Copy link
Copy Markdown
Member

/ba-g timeouts are unrelated

@adamsitnik
adamsitnik merged commit caa9958 into mainApr 23, 2026
84 of 92 checks passed
@adamsitnik
adamsitnik deleted the copilot/fix-process-safe-handle-test-failure branch April 23, 2026 19:55
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 24, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation failuring in CI

5 participants

@tmds@adamsitnik@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix race condition: set _canceled before SignalCore in ProcessWaitState - #127312

Merged
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure
Apr 23, 2026
Merged

Fix race condition: set _canceled before SignalCore in ProcessWaitState#127312
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure

Conversation

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Description

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation fails intermittently in CI because _canceled is set after SignalCore(PosixSignal.SIGKILL) returns. The reaping thread triggered by the kill signal can enter ChildReaped and read _canceled == false before the cancellation/timeout path writes true, producing exitStatus.Canceled == false.

Changes

  • Set _canceled = truebefore calling SignalCore(PosixSignal.SIGKILL) at all call sites (WaitForExitOrKillOnCancellationAsync, WaitForExitOrKillOnTimeoutCore on both Unix and Windows), so the reaping thread always observes the canceled state
  • Remove volatile from _canceled since the _gate lock acquire in ChildReaped provides the necessary memory barrier on the read side

@tmds

tmds commented Apr 23, 2026

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:

waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

The test ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation
was failing intermittently because _canceled was being set outside the _gate lock,
while ChildReaped reads it under the lock to build ProcessExitStatus. This caused
a race where ChildReaped could read _canceled as false before the cancellation
callback set it to true.
Changes:
- Add Cancel() method to SafeProcessHandle (platform-specific implementations)
- On Unix, Cancel() delegates to ProcessWaitState.Cancel() which acquires _gate
before setting _canceled, ensuring atomicity with ChildReaped
- On Windows, Cancel() wraps the existing SignalCore + Canceled property set
- Remove volatile from _canceled field since it's now protected by the lock
- Update all call sites to use Cancel() instead of directly setting Canceled
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d23310ac-167d-4a45-a389-45918504a964
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 08:23
CopilotAI changed the title [WIP] Fix ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync test failure in CIFix race condition: set _canceled under _gate lock in ProcessWaitStateApr 23, 2026
CopilotAI requested a review from adamsitnikApril 23, 2026 08:25
@adamsitnik
adamsitnik marked this pull request as ready for review April 23, 2026 08:45
CopilotAI review requested due to automatic review settings April 23, 2026 08:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an intermittent CI failure by ensuring the “canceled” state is set atomically with process reaping on Unix, eliminating a race between the cancellation/timeout path and ChildReaped exit-status construction.

Changes:

  • Add a Unix ProcessWaitState.Cancel(SafeProcessHandle) that takes _gate before signaling and setting _canceled.
  • Introduce platform-specific SafeProcessHandle.Cancel() helpers and route cancellation/timeout call sites through them (or through the wait state on Unix).
  • Remove volatile from _canceled now that access is lock-protected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.csAdds lock-protected cancel path and removes volatile on _canceled to make cancellation state consistent with ChildReaped.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csSwitches cancellation callback to call the new Cancel() helper.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csAdds Cancel() helper and uses it for timeout kill path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csAdds Cancel() helper that routes through ProcessWaitState.Cancel, and updates timeout kill path.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22fb6e3e-d0e3-4cf8-91b3-5b227dbbc8a4
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@adamsitnik

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:


waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@copilot revert all the changes and implement this suggestion.

CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 11:21

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc7251: set _canceled = true before SignalCore(PosixSignal.SIGKILL) at all call sites (Unix timeout, Unix cancellation, Windows timeout, shared cancellation callback), removed volatile from _canceled. No lock needed. All 561 tests pass.

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc72519. At all 3 call sites, _canceled is now set to true before SignalCore(PosixSignal.SIGKILL), and volatile is removed. All 561 tests pass.

CopilotAI changed the title Fix race condition: set _canceled under _gate lock in ProcessWaitStateFix race condition: set _canceled before SignalCore in ProcessWaitStateApr 23, 2026
@adamsitnik

Copy link
Copy Markdown
Member

/ba-g timeouts are unrelated

@adamsitnik
adamsitnik merged commit caa9958 into mainApr 23, 2026
84 of 92 checks passed
@adamsitnik
adamsitnik deleted the copilot/fix-process-safe-handle-test-failure branch April 23, 2026 19:55
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 24, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation failuring in CI

5 participants

@tmds@adamsitnik@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix race condition: set _canceled before SignalCore in ProcessWaitState - #127312

Merged
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure
Apr 23, 2026
Merged

Fix race condition: set _canceled before SignalCore in ProcessWaitState#127312
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure

Conversation

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Description

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation fails intermittently in CI because _canceled is set after SignalCore(PosixSignal.SIGKILL) returns. The reaping thread triggered by the kill signal can enter ChildReaped and read _canceled == false before the cancellation/timeout path writes true, producing exitStatus.Canceled == false.

Changes

  • Set _canceled = truebefore calling SignalCore(PosixSignal.SIGKILL) at all call sites (WaitForExitOrKillOnCancellationAsync, WaitForExitOrKillOnTimeoutCore on both Unix and Windows), so the reaping thread always observes the canceled state
  • Remove volatile from _canceled since the _gate lock acquire in ChildReaped provides the necessary memory barrier on the read side

@tmds

tmds commented Apr 23, 2026

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:

waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

The test ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation
was failing intermittently because _canceled was being set outside the _gate lock,
while ChildReaped reads it under the lock to build ProcessExitStatus. This caused
a race where ChildReaped could read _canceled as false before the cancellation
callback set it to true.
Changes:
- Add Cancel() method to SafeProcessHandle (platform-specific implementations)
- On Unix, Cancel() delegates to ProcessWaitState.Cancel() which acquires _gate
before setting _canceled, ensuring atomicity with ChildReaped
- On Windows, Cancel() wraps the existing SignalCore + Canceled property set
- Remove volatile from _canceled field since it's now protected by the lock
- Update all call sites to use Cancel() instead of directly setting Canceled
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d23310ac-167d-4a45-a389-45918504a964
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 08:23
CopilotAI changed the title [WIP] Fix ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync test failure in CIFix race condition: set _canceled under _gate lock in ProcessWaitStateApr 23, 2026
CopilotAI requested a review from adamsitnikApril 23, 2026 08:25
@adamsitnik
adamsitnik marked this pull request as ready for review April 23, 2026 08:45
CopilotAI review requested due to automatic review settings April 23, 2026 08:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an intermittent CI failure by ensuring the “canceled” state is set atomically with process reaping on Unix, eliminating a race between the cancellation/timeout path and ChildReaped exit-status construction.

Changes:

  • Add a Unix ProcessWaitState.Cancel(SafeProcessHandle) that takes _gate before signaling and setting _canceled.
  • Introduce platform-specific SafeProcessHandle.Cancel() helpers and route cancellation/timeout call sites through them (or through the wait state on Unix).
  • Remove volatile from _canceled now that access is lock-protected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.csAdds lock-protected cancel path and removes volatile on _canceled to make cancellation state consistent with ChildReaped.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csSwitches cancellation callback to call the new Cancel() helper.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csAdds Cancel() helper and uses it for timeout kill path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csAdds Cancel() helper that routes through ProcessWaitState.Cancel, and updates timeout kill path.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22fb6e3e-d0e3-4cf8-91b3-5b227dbbc8a4
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@adamsitnik

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:


waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@copilot revert all the changes and implement this suggestion.

CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 11:21

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc7251: set _canceled = true before SignalCore(PosixSignal.SIGKILL) at all call sites (Unix timeout, Unix cancellation, Windows timeout, shared cancellation callback), removed volatile from _canceled. No lock needed. All 561 tests pass.

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc72519. At all 3 call sites, _canceled is now set to true before SignalCore(PosixSignal.SIGKILL), and volatile is removed. All 561 tests pass.

CopilotAI changed the title Fix race condition: set _canceled under _gate lock in ProcessWaitStateFix race condition: set _canceled before SignalCore in ProcessWaitStateApr 23, 2026
@adamsitnik

Copy link
Copy Markdown
Member

/ba-g timeouts are unrelated

@adamsitnik
adamsitnik merged commit caa9958 into mainApr 23, 2026
84 of 92 checks passed
@adamsitnik
adamsitnik deleted the copilot/fix-process-safe-handle-test-failure branch April 23, 2026 19:55
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 24, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation failuring in CI

5 participants

@tmds@adamsitnik@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix race condition: set _canceled before SignalCore in ProcessWaitState - #127312

Merged
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure
Apr 23, 2026
Merged

Fix race condition: set _canceled before SignalCore in ProcessWaitState#127312
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure

Conversation

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Description

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation fails intermittently in CI because _canceled is set after SignalCore(PosixSignal.SIGKILL) returns. The reaping thread triggered by the kill signal can enter ChildReaped and read _canceled == false before the cancellation/timeout path writes true, producing exitStatus.Canceled == false.

Changes

  • Set _canceled = truebefore calling SignalCore(PosixSignal.SIGKILL) at all call sites (WaitForExitOrKillOnCancellationAsync, WaitForExitOrKillOnTimeoutCore on both Unix and Windows), so the reaping thread always observes the canceled state
  • Remove volatile from _canceled since the _gate lock acquire in ChildReaped provides the necessary memory barrier on the read side

@tmds

tmds commented Apr 23, 2026

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:

waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

The test ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation
was failing intermittently because _canceled was being set outside the _gate lock,
while ChildReaped reads it under the lock to build ProcessExitStatus. This caused
a race where ChildReaped could read _canceled as false before the cancellation
callback set it to true.
Changes:
- Add Cancel() method to SafeProcessHandle (platform-specific implementations)
- On Unix, Cancel() delegates to ProcessWaitState.Cancel() which acquires _gate
before setting _canceled, ensuring atomicity with ChildReaped
- On Windows, Cancel() wraps the existing SignalCore + Canceled property set
- Remove volatile from _canceled field since it's now protected by the lock
- Update all call sites to use Cancel() instead of directly setting Canceled
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d23310ac-167d-4a45-a389-45918504a964
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 08:23
CopilotAI changed the title [WIP] Fix ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync test failure in CIFix race condition: set _canceled under _gate lock in ProcessWaitStateApr 23, 2026
CopilotAI requested a review from adamsitnikApril 23, 2026 08:25
@adamsitnik
adamsitnik marked this pull request as ready for review April 23, 2026 08:45
CopilotAI review requested due to automatic review settings April 23, 2026 08:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an intermittent CI failure by ensuring the “canceled” state is set atomically with process reaping on Unix, eliminating a race between the cancellation/timeout path and ChildReaped exit-status construction.

Changes:

  • Add a Unix ProcessWaitState.Cancel(SafeProcessHandle) that takes _gate before signaling and setting _canceled.
  • Introduce platform-specific SafeProcessHandle.Cancel() helpers and route cancellation/timeout call sites through them (or through the wait state on Unix).
  • Remove volatile from _canceled now that access is lock-protected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.csAdds lock-protected cancel path and removes volatile on _canceled to make cancellation state consistent with ChildReaped.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csSwitches cancellation callback to call the new Cancel() helper.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csAdds Cancel() helper and uses it for timeout kill path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csAdds Cancel() helper that routes through ProcessWaitState.Cancel, and updates timeout kill path.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22fb6e3e-d0e3-4cf8-91b3-5b227dbbc8a4
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@adamsitnik

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:


waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@copilot revert all the changes and implement this suggestion.

CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 11:21

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc7251: set _canceled = true before SignalCore(PosixSignal.SIGKILL) at all call sites (Unix timeout, Unix cancellation, Windows timeout, shared cancellation callback), removed volatile from _canceled. No lock needed. All 561 tests pass.

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc72519. At all 3 call sites, _canceled is now set to true before SignalCore(PosixSignal.SIGKILL), and volatile is removed. All 561 tests pass.

CopilotAI changed the title Fix race condition: set _canceled under _gate lock in ProcessWaitStateFix race condition: set _canceled before SignalCore in ProcessWaitStateApr 23, 2026
@adamsitnik

Copy link
Copy Markdown
Member

/ba-g timeouts are unrelated

@adamsitnik
adamsitnik merged commit caa9958 into mainApr 23, 2026
84 of 92 checks passed
@adamsitnik
adamsitnik deleted the copilot/fix-process-safe-handle-test-failure branch April 23, 2026 19:55
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 24, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation failuring in CI

5 participants

@tmds@adamsitnik@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix race condition: set _canceled before SignalCore in ProcessWaitState - #127312

Merged
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure
Apr 23, 2026
Merged

Fix race condition: set _canceled before SignalCore in ProcessWaitState#127312
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure

Conversation

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Description

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation fails intermittently in CI because _canceled is set after SignalCore(PosixSignal.SIGKILL) returns. The reaping thread triggered by the kill signal can enter ChildReaped and read _canceled == false before the cancellation/timeout path writes true, producing exitStatus.Canceled == false.

Changes

  • Set _canceled = truebefore calling SignalCore(PosixSignal.SIGKILL) at all call sites (WaitForExitOrKillOnCancellationAsync, WaitForExitOrKillOnTimeoutCore on both Unix and Windows), so the reaping thread always observes the canceled state
  • Remove volatile from _canceled since the _gate lock acquire in ChildReaped provides the necessary memory barrier on the read side

@tmds

tmds commented Apr 23, 2026

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:

waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

The test ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation
was failing intermittently because _canceled was being set outside the _gate lock,
while ChildReaped reads it under the lock to build ProcessExitStatus. This caused
a race where ChildReaped could read _canceled as false before the cancellation
callback set it to true.
Changes:
- Add Cancel() method to SafeProcessHandle (platform-specific implementations)
- On Unix, Cancel() delegates to ProcessWaitState.Cancel() which acquires _gate
before setting _canceled, ensuring atomicity with ChildReaped
- On Windows, Cancel() wraps the existing SignalCore + Canceled property set
- Remove volatile from _canceled field since it's now protected by the lock
- Update all call sites to use Cancel() instead of directly setting Canceled
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d23310ac-167d-4a45-a389-45918504a964
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 08:23
CopilotAI changed the title [WIP] Fix ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync test failure in CIFix race condition: set _canceled under _gate lock in ProcessWaitStateApr 23, 2026
CopilotAI requested a review from adamsitnikApril 23, 2026 08:25
@adamsitnik
adamsitnik marked this pull request as ready for review April 23, 2026 08:45
CopilotAI review requested due to automatic review settings April 23, 2026 08:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an intermittent CI failure by ensuring the “canceled” state is set atomically with process reaping on Unix, eliminating a race between the cancellation/timeout path and ChildReaped exit-status construction.

Changes:

  • Add a Unix ProcessWaitState.Cancel(SafeProcessHandle) that takes _gate before signaling and setting _canceled.
  • Introduce platform-specific SafeProcessHandle.Cancel() helpers and route cancellation/timeout call sites through them (or through the wait state on Unix).
  • Remove volatile from _canceled now that access is lock-protected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.csAdds lock-protected cancel path and removes volatile on _canceled to make cancellation state consistent with ChildReaped.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csSwitches cancellation callback to call the new Cancel() helper.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csAdds Cancel() helper and uses it for timeout kill path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csAdds Cancel() helper that routes through ProcessWaitState.Cancel, and updates timeout kill path.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22fb6e3e-d0e3-4cf8-91b3-5b227dbbc8a4
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@adamsitnik

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:


waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@copilot revert all the changes and implement this suggestion.

CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 11:21

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc7251: set _canceled = true before SignalCore(PosixSignal.SIGKILL) at all call sites (Unix timeout, Unix cancellation, Windows timeout, shared cancellation callback), removed volatile from _canceled. No lock needed. All 561 tests pass.

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc72519. At all 3 call sites, _canceled is now set to true before SignalCore(PosixSignal.SIGKILL), and volatile is removed. All 561 tests pass.

CopilotAI changed the title Fix race condition: set _canceled under _gate lock in ProcessWaitStateFix race condition: set _canceled before SignalCore in ProcessWaitStateApr 23, 2026
@adamsitnik

Copy link
Copy Markdown
Member

/ba-g timeouts are unrelated

@adamsitnik
adamsitnik merged commit caa9958 into mainApr 23, 2026
84 of 92 checks passed
@adamsitnik
adamsitnik deleted the copilot/fix-process-safe-handle-test-failure branch April 23, 2026 19:55
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 24, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation failuring in CI

5 participants

@tmds@adamsitnik@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix race condition: set _canceled before SignalCore in ProcessWaitState - #127312

Merged
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure
Apr 23, 2026
Merged

Fix race condition: set _canceled before SignalCore in ProcessWaitState#127312
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure

Conversation

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Description

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation fails intermittently in CI because _canceled is set after SignalCore(PosixSignal.SIGKILL) returns. The reaping thread triggered by the kill signal can enter ChildReaped and read _canceled == false before the cancellation/timeout path writes true, producing exitStatus.Canceled == false.

Changes

  • Set _canceled = truebefore calling SignalCore(PosixSignal.SIGKILL) at all call sites (WaitForExitOrKillOnCancellationAsync, WaitForExitOrKillOnTimeoutCore on both Unix and Windows), so the reaping thread always observes the canceled state
  • Remove volatile from _canceled since the _gate lock acquire in ChildReaped provides the necessary memory barrier on the read side

@tmds

tmds commented Apr 23, 2026

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:

waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

The test ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation
was failing intermittently because _canceled was being set outside the _gate lock,
while ChildReaped reads it under the lock to build ProcessExitStatus. This caused
a race where ChildReaped could read _canceled as false before the cancellation
callback set it to true.
Changes:
- Add Cancel() method to SafeProcessHandle (platform-specific implementations)
- On Unix, Cancel() delegates to ProcessWaitState.Cancel() which acquires _gate
before setting _canceled, ensuring atomicity with ChildReaped
- On Windows, Cancel() wraps the existing SignalCore + Canceled property set
- Remove volatile from _canceled field since it's now protected by the lock
- Update all call sites to use Cancel() instead of directly setting Canceled
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d23310ac-167d-4a45-a389-45918504a964
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 08:23
CopilotAI changed the title [WIP] Fix ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync test failure in CIFix race condition: set _canceled under _gate lock in ProcessWaitStateApr 23, 2026
CopilotAI requested a review from adamsitnikApril 23, 2026 08:25
@adamsitnik
adamsitnik marked this pull request as ready for review April 23, 2026 08:45
CopilotAI review requested due to automatic review settings April 23, 2026 08:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an intermittent CI failure by ensuring the “canceled” state is set atomically with process reaping on Unix, eliminating a race between the cancellation/timeout path and ChildReaped exit-status construction.

Changes:

  • Add a Unix ProcessWaitState.Cancel(SafeProcessHandle) that takes _gate before signaling and setting _canceled.
  • Introduce platform-specific SafeProcessHandle.Cancel() helpers and route cancellation/timeout call sites through them (or through the wait state on Unix).
  • Remove volatile from _canceled now that access is lock-protected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.csAdds lock-protected cancel path and removes volatile on _canceled to make cancellation state consistent with ChildReaped.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csSwitches cancellation callback to call the new Cancel() helper.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csAdds Cancel() helper and uses it for timeout kill path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csAdds Cancel() helper that routes through ProcessWaitState.Cancel, and updates timeout kill path.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22fb6e3e-d0e3-4cf8-91b3-5b227dbbc8a4
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@adamsitnik

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:


waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@copilot revert all the changes and implement this suggestion.

CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 11:21

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc7251: set _canceled = true before SignalCore(PosixSignal.SIGKILL) at all call sites (Unix timeout, Unix cancellation, Windows timeout, shared cancellation callback), removed volatile from _canceled. No lock needed. All 561 tests pass.

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc72519. At all 3 call sites, _canceled is now set to true before SignalCore(PosixSignal.SIGKILL), and volatile is removed. All 561 tests pass.

CopilotAI changed the title Fix race condition: set _canceled under _gate lock in ProcessWaitStateFix race condition: set _canceled before SignalCore in ProcessWaitStateApr 23, 2026
@adamsitnik

Copy link
Copy Markdown
Member

/ba-g timeouts are unrelated

@adamsitnik
adamsitnik merged commit caa9958 into mainApr 23, 2026
84 of 92 checks passed
@adamsitnik
adamsitnik deleted the copilot/fix-process-safe-handle-test-failure branch April 23, 2026 19:55
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 24, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation failuring in CI

5 participants

@tmds@adamsitnik@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix race condition: set _canceled before SignalCore in ProcessWaitState - #127312

Merged
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure
Apr 23, 2026
Merged

Fix race condition: set _canceled before SignalCore in ProcessWaitState#127312
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure

Conversation

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Description

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation fails intermittently in CI because _canceled is set after SignalCore(PosixSignal.SIGKILL) returns. The reaping thread triggered by the kill signal can enter ChildReaped and read _canceled == false before the cancellation/timeout path writes true, producing exitStatus.Canceled == false.

Changes

  • Set _canceled = truebefore calling SignalCore(PosixSignal.SIGKILL) at all call sites (WaitForExitOrKillOnCancellationAsync, WaitForExitOrKillOnTimeoutCore on both Unix and Windows), so the reaping thread always observes the canceled state
  • Remove volatile from _canceled since the _gate lock acquire in ChildReaped provides the necessary memory barrier on the read side

@tmds

tmds commented Apr 23, 2026

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:

waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

The test ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation
was failing intermittently because _canceled was being set outside the _gate lock,
while ChildReaped reads it under the lock to build ProcessExitStatus. This caused
a race where ChildReaped could read _canceled as false before the cancellation
callback set it to true.
Changes:
- Add Cancel() method to SafeProcessHandle (platform-specific implementations)
- On Unix, Cancel() delegates to ProcessWaitState.Cancel() which acquires _gate
before setting _canceled, ensuring atomicity with ChildReaped
- On Windows, Cancel() wraps the existing SignalCore + Canceled property set
- Remove volatile from _canceled field since it's now protected by the lock
- Update all call sites to use Cancel() instead of directly setting Canceled
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d23310ac-167d-4a45-a389-45918504a964
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 08:23
CopilotAI changed the title [WIP] Fix ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync test failure in CIFix race condition: set _canceled under _gate lock in ProcessWaitStateApr 23, 2026
CopilotAI requested a review from adamsitnikApril 23, 2026 08:25
@adamsitnik
adamsitnik marked this pull request as ready for review April 23, 2026 08:45
CopilotAI review requested due to automatic review settings April 23, 2026 08:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an intermittent CI failure by ensuring the “canceled” state is set atomically with process reaping on Unix, eliminating a race between the cancellation/timeout path and ChildReaped exit-status construction.

Changes:

  • Add a Unix ProcessWaitState.Cancel(SafeProcessHandle) that takes _gate before signaling and setting _canceled.
  • Introduce platform-specific SafeProcessHandle.Cancel() helpers and route cancellation/timeout call sites through them (or through the wait state on Unix).
  • Remove volatile from _canceled now that access is lock-protected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.csAdds lock-protected cancel path and removes volatile on _canceled to make cancellation state consistent with ChildReaped.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csSwitches cancellation callback to call the new Cancel() helper.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csAdds Cancel() helper and uses it for timeout kill path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csAdds Cancel() helper that routes through ProcessWaitState.Cancel, and updates timeout kill path.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22fb6e3e-d0e3-4cf8-91b3-5b227dbbc8a4
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@adamsitnik

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:


waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@copilot revert all the changes and implement this suggestion.

CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 11:21

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc7251: set _canceled = true before SignalCore(PosixSignal.SIGKILL) at all call sites (Unix timeout, Unix cancellation, Windows timeout, shared cancellation callback), removed volatile from _canceled. No lock needed. All 561 tests pass.

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc72519. At all 3 call sites, _canceled is now set to true before SignalCore(PosixSignal.SIGKILL), and volatile is removed. All 561 tests pass.

CopilotAI changed the title Fix race condition: set _canceled under _gate lock in ProcessWaitStateFix race condition: set _canceled before SignalCore in ProcessWaitStateApr 23, 2026
@adamsitnik

Copy link
Copy Markdown
Member

/ba-g timeouts are unrelated

@adamsitnik
adamsitnik merged commit caa9958 into mainApr 23, 2026
84 of 92 checks passed
@adamsitnik
adamsitnik deleted the copilot/fix-process-safe-handle-test-failure branch April 23, 2026 19:55
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 24, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation failuring in CI

5 participants

@tmds@adamsitnik@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix race condition: set _canceled before SignalCore in ProcessWaitState - #127312

Merged
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure
Apr 23, 2026
Merged

Fix race condition: set _canceled before SignalCore in ProcessWaitState#127312
adamsitnik merged 4 commits into
mainfrom
copilot/fix-process-safe-handle-test-failure

Conversation

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Description

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation fails intermittently in CI because _canceled is set after SignalCore(PosixSignal.SIGKILL) returns. The reaping thread triggered by the kill signal can enter ChildReaped and read _canceled == false before the cancellation/timeout path writes true, producing exitStatus.Canceled == false.

Changes

  • Set _canceled = truebefore calling SignalCore(PosixSignal.SIGKILL) at all call sites (WaitForExitOrKillOnCancellationAsync, WaitForExitOrKillOnTimeoutCore on both Unix and Windows), so the reaping thread always observes the canceled state
  • Remove volatile from _canceled since the _gate lock acquire in ChildReaped provides the necessary memory barrier on the read side

@tmds

tmds commented Apr 23, 2026

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:

waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

The test ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation
was failing intermittently because _canceled was being set outside the _gate lock,
while ChildReaped reads it under the lock to build ProcessExitStatus. This caused
a race where ChildReaped could read _canceled as false before the cancellation
callback set it to true.
Changes:
- Add Cancel() method to SafeProcessHandle (platform-specific implementations)
- On Unix, Cancel() delegates to ProcessWaitState.Cancel() which acquires _gate
before setting _canceled, ensuring atomicity with ChildReaped
- On Windows, Cancel() wraps the existing SignalCore + Canceled property set
- Remove volatile from _canceled field since it's now protected by the lock
- Update all call sites to use Cancel() instead of directly setting Canceled
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d23310ac-167d-4a45-a389-45918504a964
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 08:23
CopilotAI changed the title [WIP] Fix ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync test failure in CIFix race condition: set _canceled under _gate lock in ProcessWaitStateApr 23, 2026
CopilotAI requested a review from adamsitnikApril 23, 2026 08:25
@adamsitnik
adamsitnik marked this pull request as ready for review April 23, 2026 08:45
CopilotAI review requested due to automatic review settings April 23, 2026 08:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an intermittent CI failure by ensuring the “canceled” state is set atomically with process reaping on Unix, eliminating a race between the cancellation/timeout path and ChildReaped exit-status construction.

Changes:

  • Add a Unix ProcessWaitState.Cancel(SafeProcessHandle) that takes _gate before signaling and setting _canceled.
  • Introduce platform-specific SafeProcessHandle.Cancel() helpers and route cancellation/timeout call sites through them (or through the wait state on Unix).
  • Remove volatile from _canceled now that access is lock-protected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.csAdds lock-protected cancel path and removes volatile on _canceled to make cancellation state consistent with ChildReaped.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csSwitches cancellation callback to call the new Cancel() helper.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csAdds Cancel() helper and uses it for timeout kill path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csAdds Cancel() helper that routes through ProcessWaitState.Cancel, and updates timeout kill path.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22fb6e3e-d0e3-4cf8-91b3-5b227dbbc8a4
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@adamsitnik

Copy link
Copy Markdown
Member

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

Suggestion: remove the volatile, don't take a lock, and do this:


waitState._canceled = true;
SignalCore(PosixSignal.SIGKILL);

@copilot revert all the changes and implement this suggestion.

CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 11:21

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc7251: set _canceled = true before SignalCore(PosixSignal.SIGKILL) at all call sites (Unix timeout, Unix cancellation, Windows timeout, shared cancellation callback), removed volatile from _canceled. No lock needed. All 561 tests pass.

Add Cancel(SafeProcessHandle) method to ProcessWaitState.Unix.cs that sets _canceled under _gate lock

...

Reverted all previous changes and implemented the simpler approach in 6bc72519. At all 3 call sites, _canceled is now set to true before SignalCore(PosixSignal.SIGKILL), and volatile is removed. All 561 tests pass.

CopilotAI changed the title Fix race condition: set _canceled under _gate lock in ProcessWaitStateFix race condition: set _canceled before SignalCore in ProcessWaitStateApr 23, 2026
@adamsitnik

Copy link
Copy Markdown
Member

/ba-g timeouts are unrelated

@adamsitnik
adamsitnik merged commit caa9958 into mainApr 23, 2026
84 of 92 checks passed
@adamsitnik
adamsitnik deleted the copilot/fix-process-safe-handle-test-failure branch April 23, 2026 19:55
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 24, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProcessSafeHandle_WaitForExitOrKillOnCancellationAsync_KillsOnCancellation failuring in CI

5 participants

@tmds@adamsitnik@jkotas