Skip to content

Specialize CoreLib synchronization for single-threaded CoreCLR - #135903

Merged
jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/single-threaded-synchronization
Oct 27, 2026
Merged

jkoritzinsky merged 11 commits into
mainfrom
dev/jkoritzinsky/single-threaded-synchronization

Conversation

@jkoritzinsky

@jkoritzinsky jkoritzinsky commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

True single-threaded CoreCLR platforms still carry synchronization machinery designed for competing threads, even when a blocking wait cannot make progress. This specializes those paths for browser/WASI while retaining the existing implementations for multithreaded configurations and Mono.

Approach

  • Select substantially different implementations through .SingleThreading.cs and .MultiThreading.cs files, keeping common API wrappers shared. This covers object-header locks, Lock, Condition, low-level locks, slim primitives, SpinLock, and task blocking paths.
  • Use a dedicated single-threaded wait subsystem with direct signal/recursion state instead of locks, waiter registration, temporary object arrays, ownership records, or abandonment machinery. Named mutexes retain only the lifetime accounting needed by multiple handles.
  • Store pending interruption directly on Thread, removing the single-threaded ThreadWaitInfo type and allocation. Update the native Thread layout, binder checks, source selection, and Wasm static imports accordingly.
  • Reject unsupported TaskFactory.FromAsync waits before allocating/attaching a promise, avoiding an orphaned AttachedToParent child when the operation throws.

Behavior and compatibility

Already-satisfied and zero-timeout wait-handle operations retain signal consumption, recursive ownership, validation, and interruption behavior. Unsatisfied wait-handle operations with positive finite timeouts sleep for the requested timeout and then return the normal timeout result. Contended anonymous SpinLock.TryEnter calls with positive finite timeouts likewise sleep and return without acquiring the lock. These finite delays are supported for compatibility and do not pump the managed event loop.

Unsatisfied infinite wait-handle operations, infinite sleep, and contended anonymous SpinLock acquisitions with infinite timeouts throw PlatformNotSupportedException rather than entering waits or spin loops that cannot make progress. These are intentional behavioral changes that need breaking-change documentation.

Finite Thread.Sleep and self-Thread.Join delays remain supported through native nanosleep, without a monitor or managed event-loop pumping. Task inline execution and blocking-enumeration deferred failure are preserved. Shared multithreading implementations retain the guards needed by single-threaded Mono and threaded-Wasm JS-interop restrictions. No public API signatures or Mono-only sources change.

Validation

Local validation was performed during implementation; it was not rerun after the final manual review edits.

  • Matching native/managed Release builds and runtime-pack staging for browser and WASI passed. Published CoreLib/native artifacts were checked against the rebuilt outputs.
  • Browser interpreter threading, Thread, and WaitHandle coverage passed; focused browser ReadyToRun Thread/WaitHandle coverage passed.
  • WASI threading, Thread, and WaitHandle coverage passed using freshly generated app-specific hosts. On Windows, the existing response-file escaping issue required linking those hosts with --rsp-quoting=windows; no publisher changes are included.
  • Windows Checked Thread/task/thread-pool coverage passed. Four mutex-impersonation failures reproduced on the preserved baseline; the remaining threading coverage passed with only that unchanged method filtered.
  • Multithreaded Linux CoreCLR and Windows NativeAOT CoreLib compilation passed. Mono source selection was checked; Linux/NativeAOT execution and Mono execution were not performed.
  • Standalone probes covered exact mutex-recursion/semaphore-overflow boundaries, interruption state, and allocation removal. Allocation assertions are not retained in the repository tests.

ReadyToRun CoreLib size

Compared PR head ece2693fe1213a604e59f8f105ccd8eb0e8666b5 against 691418a89493b63a87f3a19862ec573b2cc3eb96, found using git merge-base HEAD origin/main after fetching origin/main.

Measured the full, untrimmed browser-wasm Release System.Private.CoreLib.wasm. The verified clean merge-base artifact was reused and the final PR head was freshly rebuilt with unchanged compiler sources and build configuration.

Format Merge-base (bytes) PR head (bytes) Reduction
Raw 24,428,812 24,288,461 140,351 bytes (0.575%)
Brotli 4,091,056 4,069,757 21,299 bytes (0.521%)

Both Brotli measurements use .NET BrotliStream with CompressionLevel.SmallestSize. This measures R2R CoreLib alone, including its embedded IL; it excludes dotnet.native.wasm and is not a final trimmed-application download-size measurement.

Development measurements

Single-threaded WASI managed allocation measurements:

Operation Before After
AutoResetEvent construction 64 B 52 B
Mutex construction 80 B 52 B
WaitAll without a cached subsystem object array 20 B 0 B
First Thread.Interrupt on an unstarted thread 12 B 0 B

Desktop BenchmarkDotNet comparisons retained the same allocations for completed APM calls (256 B) and blocking enumeration (80 B). Small timing differences reversed on repeat; normalized non-tiered WaitAll/iterator instructions were identical. These are development measurements, not a final application-download-size or Wasm-throughput claim.

Note

This description and portions of the implementation were generated with GitHub Copilot and reviewed and adjusted by the author.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jkoritzinsky jkoritzinsky added the os-browser Browser variant of arch-wasm label Oct 15, 2026
@jkoritzinsky
jkoritzinsky requested a review from VSadov October 15, 2026 22:47
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @VSadov
See info in area-owners.md if you want to be subscribed.

jkoritzinsky and others added 4 commits October 16, 2026 19:38
Honor positive finite wait and SpinLock timeouts, retain single-threaded Mono guards, restore original timeout assertions, and remove the redundant wait-array helper.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve satisfied and zero-timeout ManualResetEventSlim, SemaphoreSlim, Task and Monitor waits from #135413 in both source partitions. Keep validation, recursion and wait-handle state behavior consistent, and retain the upstream Roslyn workspace test ungates.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use consistent non-Windows Thread wait-info guards, rely on the conditional runtime guard helper, and match the threaded-browser Monitor annotation to the existing reference contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

🟡 Changes recommended

The timing test can fail on Windows, and the finite-timeout compatibility description conflicts with the implementation and tests.

2 open issues
What changed in this PR

Specializes CoreLib synchronization for single-threaded browser/WASI CoreCLR, reducing synchronization state while retaining separate multithreaded implementations.

Changes:

  • Splits locks, synchronization primitives, and task waits into threading-specific implementations.
  • Adds native finite sleep and stores pending interruptions directly on Thread.
  • Adds compatibility tests and updates source selection, native layouts, and Wasm imports.

Builds and tests were not run during this review.

File Description
src/​native/​libs/​System.Native/​pal_time.h Declares native sleep.
src/​native/​libs/​System.Native/​pal_time.c Implements interruption-retrying native sleep.
src/​native/​libs/​System.Native/​entrypoints.c Registers sleep entry point.
src/​libraries/​System.Threading/​tests/​SpinLockTests.cs Tests ownership and timeout behavior.
src/​libraries/​System.Threading/​tests/​SemaphoreSlimTests.cs Tests permits, cancellation, and waits.
src/​libraries/​System.Threading/​tests/​ReaderWriterLockSlimTests.cs Tests recursion and ownership counts.
src/​libraries/​System.Threading/​tests/​MutexTests.cs Tests named-handle ownership and lifetime.
src/​libraries/​System.Threading/​tests/​MonitorTests.cs Tests headers, recursion, and wait validation.
src/​libraries/​System.Threading/​tests/​ManualResetEventSlimTests.cs Tests state and blocking restrictions.
src/​libraries/​System.Threading.ThreadPool/​tests/​RegisteredWaitTests.cs Tests registration validation.
src/​libraries/​System.Threading.Thread/​tests/​ThreadTests.cs Tests sleep and interruption behavior.
src/​libraries/​System.Runtime/​tests/​System.Threading.Tasks.Tests/​TaskFactory/​TaskFactory_FromAsyncTests.cs Tests completed and unsupported asynchronous results.
src/​libraries/​System.Runtime/​tests/​System.Threading.Tasks.Tests/​Task/​TaskArgumentValidationTests.cs Tests task waits and inline execution.
src/​libraries/​System.Runtime/​tests/​System.Threading.Tasks.Tests/​Task/​AsyncEnumerableToBlockingEnumerableTests.cs Tests deferred enumeration failure.
src/​libraries/​System.Runtime/​tests/​System.Runtime.Tests/​System/​Threading/​WaitHandleTests.cs Tests signals, timeouts, and interruptions.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​WaitSubsystem.WaitableObject.Unix.MultiThreading.cs Isolates waiter and ownership machinery.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​WaitSubsystem.WaitableObject.Unix.cs Delegates multithreaded wait mechanics.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​WaitSubsystem.WaitableObject.SingleThreading.cs Adds compact signal and mutex state.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​WaitSubsystem.Unix.cs Updates documentation and array cleanup.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​WaitSubsystem.ThreadWaitInfo.Unix.MultiThreading.cs Adjusts switch-block formatting.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​WaitSubsystem.SingleThreading.cs Adds single-threaded handles, waits, and sleep.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​ThreadPoolWorkQueue.cs Removes redundant wrapper guards.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Thread.SingleThreading.cs Removes empty join notification helper.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Tasks/​TaskFactory_T.SingleThreading.cs Rejects unsupported waits before promise creation.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Tasks/​TaskFactory_T.MultiThreading.cs Retains multithreaded FromAsync implementation.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Tasks/​TaskFactory_T.cs Delegates to threading-specific implementation.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Tasks/​TaskAsyncEnumerableExtensions.ToBlockingEnumerable.SingleThreading.cs Defers unsupported enumeration failure.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Tasks/​TaskAsyncEnumerableExtensions.ToBlockingEnumerable.MultiThreading.cs Retains blocking iterator implementation.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Tasks/​TaskAsyncEnumerableExtensions.ToBlockingEnumerable.cs Shares the public enumeration wrapper.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Tasks/​Task.SingleThreading.cs Specializes task wait paths.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Tasks/​Task.MultiThreading.cs Isolates multithreaded task waits.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​SpinLock.SingleThreading.cs Adds simplified ownership and timeout handling.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​SpinLock.MultiThreading.cs Retains spinning and atomic ownership.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​SemaphoreSlim.SingleThreading.cs Specializes permit and waiter management.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​ReaderWriterLockSlim.SingleThreading.cs Tracks single-threaded recursion counts.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Monitor.SingleThreading.cs Provides single-threaded wait entry point.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Monitor.MultiThreading.cs Provides multithreaded wait entry point.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Monitor.cs Moves wait entry point into variants.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​ManualResetEventSlim.SingleThreading.cs Simplifies event state and waits.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​LowLevelLock.SingleThreading.cs Adds minimal lock state.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​LowLevelLock.MultiThreading.cs Retains atomic locking and blocking.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Lock.SingleThreading.cs Specializes recursive locking and monitor operations.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Condition.SingleThreading.cs Delegates condition operations to Lock.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Condition.MultiThreading.cs Retains condition waiter machinery.
src/​libraries/​System.Private.CoreLib/​src/​System.Private.CoreLib.Shared.projitems Selects threading-specific shared sources.
src/​libraries/​Common/​src/​Interop/​Unix/​System.Native/​Interop.ThreadSleep.cs Adds managed native-sleep import.
src/​coreclr/​vm/​wasm/​wasi/​callhelpers-pinvoke.cpp Updates WASI static imports.
src/​coreclr/​vm/​wasm/​browser/​callhelpers-pinvoke.cpp Updates browser static imports.
src/​coreclr/​vm/​object.h Aligns native Thread fields.
src/​coreclr/​vm/​corelib.h Updates field bindings and feature guards.
src/​coreclr/​System.Private.CoreLib/​System.Private.CoreLib.csproj Selects object-header implementation.
src/​coreclr/​System.Private.CoreLib/​src/​System/​Threading/​Thread.CoreCLR.SingleThreading.cs Stores pending interruption directly.
src/​coreclr/​System.Private.CoreLib/​src/​System/​Threading/​Thread.CoreCLR.Multithreading.cs Houses multithreaded wait-info access.
src/​coreclr/​System.Private.CoreLib/​src/​System/​Threading/​Thread.CoreCLR.cs Restricts wait fields and exit cleanup.
src/​coreclr/​System.Private.CoreLib/​src/​System/​Threading/​ObjectHeader.CoreCLR.SingleThreading.cs Adds simplified thin-lock operations.
src/​coreclr/​System.Private.CoreLib/​src/​System/​Threading/​ObjectHeader.CoreCLR.MultiThreading.cs Retains atomic thin-lock operations.
src/​coreclr/​System.Private.CoreLib/​src/​System/​Threading/​ObjectHeader.CoreCLR.cs Keeps shared object-header functionality.
src/​coreclr/​System.Private.CoreLib/​src/​System/​Threading/​ManagedThreadId.CoreCLR.cs Names the single-thread ID constant.

Review effort: Balanced

Use the shared single-threaded Task implementation for Mono builds without multithreading and remove the unreachable fallback branches from Task.MultiThreading.cs. Enable the existing single-threaded Task contract tests on Mono.

Restore the semaphore blocking-path guard and restrict the SpinLock timeout timing test to single-threaded CoreCLR.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@jkotas jkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@jkotas
jkotas requested a review from pavelsavara October 22, 2026 03:23
@steveisok
steveisok requested a review from lewing October 22, 2026 11:01
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread src/mono/wasm/threads.md Resolved
Comment thread src/mono/wasm/threads.md Resolved
@pavelsavara

pavelsavara commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

NAOT doesn't compile here. Overall LGTM

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Regenerate WASI call helpers from the merged Release framework to retain upstream compression imports and specialized single-threaded waits.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member Author

/ba-g failures are in #136534

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Threading os-browser Browser variant of arch-wasm

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants