Skip to content

ThreadingWin: force Thread::suspend to be synchronous#183

Merged
Jarred-Sumner merged 1 commit intomainfrom
claude/threading-win-sync-suspend
Apr 15, 2026
Merged

ThreadingWin: force Thread::suspend to be synchronous#183
Jarred-Sumner merged 1 commit intomainfrom
claude/threading-win-sync-suspend

Conversation

@sosukesuzuki
Copy link
Copy Markdown

On multi-core Windows systems, SuspendThread merely requests a suspension
from the kernel -- the target thread may continue to execute user-mode
instructions briefly after SuspendThread returns, until the kernel
reschedules it away. Callers that read registers (MachineStackMarker)
happen to force a sync by calling GetThreadContext right after suspend,
but callers that only want the target stopped -- notably the libpas
scavenger's pasSuspenderBeginSuspend path -- would otherwise race the
mutator: the scavenger decommits TLC pages while the mutator is still
writing to them, violating pas_thread_suspender.h's contract and
causing intermittent heap corruption or access violations.

Call GetThreadContext once with CONTEXT_INTEGER immediately after a
successful SuspendThread. GetThreadContext synchronizes with the
scheduler and does not return until the target is actually stopped.
On failure (rare -- handle invalidation), undo the SuspendThread and
surface the error code so the caller sees a clean Expected failure
instead of a half-suspended thread.

Add a regression test in TestWebKitAPI: a worker busy-increments a
counter; while suspended, the counter must not advance. The test is
cross-platform (Darwin mach_thread_suspend and Linux signal-based
suspend are already synchronous) and would have flagged this bug had
it existed then.

On multi-core Windows systems, SuspendThread merely requests a suspension
from the kernel -- the target thread may continue to execute user-mode
instructions briefly after SuspendThread returns, until the kernel
reschedules it away. Callers that read registers (MachineStackMarker)
happen to force a sync by calling GetThreadContext right after suspend,
but callers that only want the target stopped -- notably the libpas
scavenger's pasSuspenderBeginSuspend path -- would otherwise race the
mutator: the scavenger decommits TLC pages while the mutator is still
writing to them, violating pas_thread_suspender.h's contract and
causing intermittent heap corruption or access violations.

Call GetThreadContext once with CONTEXT_INTEGER immediately after a
successful SuspendThread. GetThreadContext synchronizes with the
scheduler and does not return until the target is actually stopped.
On failure (rare -- handle invalidation), undo the SuspendThread and
surface the error code so the caller sees a clean Expected failure
instead of a half-suspended thread.

Add a regression test in TestWebKitAPI: a worker busy-increments a
counter; while suspended, the counter must not advance. The test is
cross-platform (Darwin mach_thread_suspend and Linux signal-based
suspend are already synchronous) and would have flagged this bug had
it existed then.
@github-actions
Copy link
Copy Markdown

Preview Builds

Commit Release Date
1eed2acf autobuild-preview-pr-183-1eed2acf 2026-04-15 02:27:03 UTC

@sosukesuzuki sosukesuzuki marked this pull request as ready for review April 15, 2026 04:16
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

Walkthrough

This change enhances the Windows thread suspension mechanism by adding synchronous verification. The Thread::suspend method now verifies suspension completion via GetThreadContext before returning, with proper error handling. A corresponding test validates the synchronous suspension behavior.

Changes

Cohort / File(s) Summary
Thread Suspension Verification
Source/WTF/wtf/win/ThreadingWin.cpp
Updated Thread::suspend to verify suspension completion by calling GetThreadContext after SuspendThread. On verification failure, the thread is resumed and an error is returned. Success path returns only on successful verification, not just successful suspend call.
Synchronous Suspension Test
Tools/TestWebKitAPI/Tests/WTF/Threading.cpp
Added TEST(WTF_Thread, SuspendIsSynchronous) that validates thread suspension prevents the target thread from advancing during suspension windows. Uses an atomic counter to detect any thread progress while suspended, asserting zero violations. Also added <atomic> header dependency.
🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description explains the problem, solution, and testing approach clearly. However, it lacks the required template elements: no Bugzilla bug reference, no 'Reviewed by' line, and no structured file change listing. Add the Bugzilla bug reference (https://bugs.webkit.org/show_bug.cgi?id=#####), include 'Reviewed by NOBODY (OOPS!).' line, and format the file changes section per the template structure.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: making Thread::suspend synchronous on Windows, which is the core objective of the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Source/WTF/wtf/win/ThreadingWin.cpp`:
- Around line 220-221: The check after SuspendThread in ThreadingWin.cpp
incorrectly returns the sentinel value (DWORD)-1 instead of the real error;
replace the early return that calls makeUnexpected(result) when result ==
(DWORD)-1 with code that captures the real error via GetLastError() and returns
makeUnexpected(errorCode) (i.e., call GetLastError() into a DWORD and pass that
to makeUnexpected). Update the SuspendThread failure branch so callers like
MachineStackMarker.cpp receive the actual OS error code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7cd3cf05-dcba-4204-96c7-3ff7b9ec70ac

📥 Commits

Reviewing files that changed from the base of the PR and between 8c97304 and 1eed2ac.

📒 Files selected for processing (2)
  • Source/WTF/wtf/win/ThreadingWin.cpp
  • Tools/TestWebKitAPI/Tests/WTF/Threading.cpp

Comment on lines +220 to +221
if (result == (DWORD)-1)
return makeUnexpected(result);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inconsistent error handling: return actual error code, not the failure sentinel.

When SuspendThread fails, result is (DWORD)-1, which is the failure sentinel—not the actual error code. This is inconsistent with line 232 where GetLastError() is correctly called. Callers like MachineStackMarker.cpp may inspect the error value for diagnostics.

🐛 Proposed fix
     DWORD result = SuspendThread(m_handle);
-    if (result == (DWORD)-1)
-        return makeUnexpected(result);
+    if (result == (DWORD)-1) {
+        DWORD error = GetLastError();
+        return makeUnexpected(error);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (result == (DWORD)-1)
return makeUnexpected(result);
if (result == (DWORD)-1) {
DWORD error = GetLastError();
return makeUnexpected(error);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/WTF/wtf/win/ThreadingWin.cpp` around lines 220 - 221, The check after
SuspendThread in ThreadingWin.cpp incorrectly returns the sentinel value
(DWORD)-1 instead of the real error; replace the early return that calls
makeUnexpected(result) when result == (DWORD)-1 with code that captures the real
error via GetLastError() and returns makeUnexpected(errorCode) (i.e., call
GetLastError() into a DWORD and pass that to makeUnexpected). Update the
SuspendThread failure branch so callers like MachineStackMarker.cpp receive the
actual OS error code.

@Jarred-Sumner Jarred-Sumner merged commit c0eac76 into main Apr 15, 2026
35 checks passed
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