[native] Replace std::mutex with pthread_mutex_t in the CoreCLR host - #12541
Open
simonrozsival wants to merge 7 commits into
Open
[native] Replace std::mutex with pthread_mutex_t in the CoreCLR host#12541simonrozsival wants to merge 7 commits into
simonrozsival wants to merge 7 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR reduces the CoreCLR host’s dependence on libc++ by replacing std::mutex/std::lock_guard usage with a minimal pthread-backed Mutex/MutexGuard wrapper in the shared native runtime-base headers.
Changes:
- Add
MutexandMutexGuardwrappers aroundpthread_mutex_tinruntime-base/mutex.hh. - Update CoreCLR host code paths (assembly store, FastDev assemblies, DSO loader, startup-aware lock) to use the new wrapper instead of
<mutex>. - Update shared timing code to use the new wrapper (affecting other runtime lanes via common sources).
Show a summary per file
| File | Description |
|---|---|
| src/native/common/include/runtime-base/timing.hh | Switch timing sequence locking to Mutex/MutexGuard. |
| src/native/common/include/runtime-base/mutex.hh | Introduce pthread-backed Mutex and MutexGuard. |
| src/native/clr/include/runtime-base/startup-aware-lock.hh | Replace std::mutex reference with Mutex. |
| src/native/clr/include/runtime-base/monodroid-dl.hh | Replace static std::mutex with Mutex for DSO handle write lock. |
| src/native/clr/include/host/fastdev-assemblies.hh | Replace override directory lock from std::mutex to Mutex. |
| src/native/clr/include/host/assembly-store.hh | Replace assembly decompress lock from std::mutex to Mutex. |
| src/native/clr/host/fastdev-assemblies.cc | Replace std::lock_guard usage with MutexGuard. |
| src/native/clr/host/assembly-store.cc | Replace internal state_lock and std::lock_guard usage with Mutex/MutexGuard. |
Review details
- Files reviewed: 8/8 changed files
- Comments generated: 2
- Review effort level: Lite
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 27, 2026 15:47
9b98570 to
83ca915
Compare
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 27, 2026 16:09
83ca915 to
d43d7d7
Compare
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 27, 2026 19:29
11cdb27 to
36f1cc4
Compare
simonrozsival
changed the base branch from
dev/simonrozsival/clr-timing-raw-pointer
to
dev/simonrozsival/clr-remove-std-format
August 27, 2026 19:34
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 27, 2026 21:42
36f1cc4 to
85c0bf3
Compare
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 28, 2026 07:54
85c0bf3 to
875ef58
Compare
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 28, 2026 08:47
875ef58 to
a3811d6
Compare
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 28, 2026 08:55
a3811d6 to
82ea5c4
Compare
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 28, 2026 09:51
82ea5c4 to
ba25053
Compare
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 28, 2026 10:29
ba25053 to
2507635
Compare
`std::mutex` and `std::lock_guard` are thin wrappers over pthreads, but using them makes the runtime depend on libc++. Add `Mutex` and `MutexGuard` in `common/include/runtime-base/mutex.hh` and use them instead. `Mutex` uses `PTHREAD_MUTEX_INITIALIZER` as a default member initializer and has a `constexpr` default constructor, so the four static instances (`assembly_decompress_mutex`, `override_dir_lock`, `dso_handle_write_lock` and `Timing::sequence_lock`) are constant-initialized. That means they need neither dynamic initialization nor a thread-safe initialization guard - the number of `__cxa_guard_*` references is unchanged by this commit. This drops all eight `std::__ndk1::mutex` references from `libnet-android.release-static-release.a`, taking the host from 48 to 40 undefined libc++ symbols. `Timing` lives in the shared `common` sources, so this affects the MonoVM host too; it still links libc++ for other reasons, and the behaviour is unchanged either way. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Reuse a single pthread mutex wrapper instead of adding a second one. `mono/shared/cppcompat.hh` already contained `xamarin::android::mutex` and `xamarin::android::lock_guard`, added for exactly the same reason: `<mutex>` makes the runtime depend on libc++. Rather than maintain two wrappers with the same purpose, delete `cppcompat.hh` and move the MonoVM host over to the shared `Mutex`/`MutexGuard` in `common/include/runtime-base/mutex.hh`. `Mutex` is the stricter of the two: it deletes the copy and move operations, which the old `mutex` left implicitly defined even though copying a `pthread_mutex_t` is never correct. It is also explicitly `constexpr` default constructible, so static instances stay constant-initialized. Also refresh the two comments explaining why `NDEBUG` is defined before including `robin_map.h`. They claimed `<mutex>` "conflicts with our std::mutex definition in cppcompat.hh", which stopped being true once the wrapper moved into the `xamarin::android` namespace. The hack is still worth keeping, but the real reason is that `<iostream>` and `<mutex>` would both pull in libc++. Finally, value-initialize `dso_handle_write_lock` for consistency with the other static `Mutex` instances. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Drop the RAII guard in favour of calling pthread_mutex_lock/unlock through Mutex directly. Critical sections that used to return, break or continue while holding the lock now delegate to a `_locked` helper that holds the branching logic, so each locked region has exactly one entry and one exit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Drop the Mutex class and use pthread_mutex_t with pthread_mutex_lock/unlock at the call sites. PTHREAD_MUTEX_INITIALIZER keeps the static instances constant-initialized, so they still need no thread-safe initialization guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Keep cppcompat.hh and the MonoVM hosts's use of it as they were. The CoreCLR host no longer shares a mutex wrapper with MonoVM, so there is no reason for this change to reach into src/native/mono. Timing lives in the shared common sources, so it is still converted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
The previous commit split every locked region that contained an early exit into an outer method (which locks) and an inner `_locked` method (which holds the logic). That kept the lock/unlock pairing obvious, but it introduced six new methods and a helper enum purely to work around `return` statements. Restructure the locked regions in place instead: use a result flag plus `break`/`if`-`else` so control always falls through to the unlock, and move the early `return` after it. This drops all six helpers along with the `ReserveResult` enum and keeps the diff against the original code much smaller. * `writer_loop` uses `have_request` / `write_failed` * `enqueue_write` restores the original `queue_full` bool and adds `writes_allowed` * `get_available_sequence` uses `ret == nullptr` + `break` * `open_assembly` inverts the `opendir` check and re-tests `override_dir_fd` after unlocking No behavioural change. libc++ references are unchanged at 40 (CoreCLR) and 0 (NativeAOT), and `__cxa_guard_*` stays at 8, confirming the statics are still constant-initialized. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
The previous two commits reshaped the locked regions to avoid returning while the lock was held: first by splitting them into `_locked` helpers, then by threading result flags so control fell through to the unlock. Both worked, but both restructured code that did not otherwise need to change. Just call `pthread_mutex_unlock` immediately before the early `return` instead. The regions keep their original shape, so the diff against the pre-existing code is now purely mechanical -- a type change, a `std::lock_guard` turning into a `pthread_mutex_lock`, and an added unlock. Churn against the base drops from 145 changed lines to 62. This is safe because the native runtime is built with `-fno-exceptions` (verified in `compile_commands.json`), so there is no unwind path that `std::lock_guard` would have covered and a manual unlock would miss. Verified that every `return` inside a locked region is immediately preceded by an unlock of that mutex, across all 13 regions. libc++ references remain 40 (CoreCLR) and 0 (NativeAOT), and `__cxa_guard_*` stays at 8. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
simonrozsival
force-pushed
the
dev/simonrozsival/clr-replace-std-mutex
branch
from
August 28, 2026 12:06
2507635 to
df1152c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #12533 (drop
libc++from the CoreCLR host). Stacked on top of #12534.std::mutexis a thin wrapper overpthread_mutex_t— it holds one as its only member and itslock/unlockforward straight topthread_mutex_lock/pthread_mutex_unlock. Using it in the host buys us nothing over the primitive itself, but it drags<mutex>into six translation units.This replaces it with
pthread_mutex_tdirectly.What
std::mutex→pthread_mutex_t x = PTHREAD_MUTEX_INITIALIZERstd::lock_guard→ explicitpthread_mutex_lock (&x)/pthread_mutex_unlock (&x), with the unlock placed immediately before any earlyreturnStartupAwareLocknow takes apthread_mutex_t&#include <mutex>removed; explicit#include <pthread.h>added where the type is usedNo locked region changed shape. Every hunk is a type change, a lock call, or an added unlock — there is no restructured logic to re-verify.
clr/host/assembly-store.cc+include/host/assembly-store.hhstate_lockclr/host/fastdev-assemblies.cc+include/host/fastdev-assemblies.hhoverride_dir_lock(DEBUG-only)clr/include/runtime-base/monodroid-dl.hhdso_handle_write_lockclr/include/runtime-base/startup-aware-lock.hhpthread_mutex_t&common/include/runtime-base/timing.hhsequence_locktiming.hhis the only file outsideclr/. It usedstd::mutexdirectly, so it cannot be skipped; MonoVM builds clean with the change andmono/is otherwise untouched (MonoVM has its ownxamarin::android::mutexinmono/shared/cppcompat.hh, which already wrapped pthreads and is left alone).Effect
This removes the last
#include <mutex>in the repository:<mutex>std::mutex/std::lock_guardusesIt also drops eight undefined references, since
lock (),unlock ()and the destructor are out-of-line inlibc++rather than header-inlined:std::__ndk1::mutex::lock()std::__ndk1::mutex::unlock()std::__ndk1::mutex::~mutex()__cxa_guard_*The link-time
libc++requirement only disappears when every cause reaches zero, so this is one of several prerequisites rather than a self-sufficient win — the remaining causes (operator new/delete[],__cxa_guard_*,std::string,__libcpp_verbose_abort) are tracked in #12533.Why manual unlock is safe here
The native runtime is built with
-fno-exceptions(confirmed incompile_commands.json), so there is no unwind path thatstd::lock_guardwould have covered and a manual unlock would miss. Everyreturninside a locked region was verified to be immediately preceded by an unlock of that mutex, across all 13 regions.Verification
fastdev-assemblies.ccis#if defined(DEBUG)and therefore not built in Release, so it was additionally compiled with-DDEBUGto confirm it is correct.libc++references: CoreCLR 55 → 47, NativeAOT 0 — the eight removed are exactly themutexmembers listed above, confirmed by diffing the demangled undefined-symbol sets of the two archives.__cxa_guard_*undefined references stay at 10. This is the check that matters for correctness of the conversion:pthread_mutex_tis a POD andPTHREAD_MUTEX_INITIALIZERis a constant initializer, so the four statics stay constant-initialized. Had they needed dynamic initialization we would have traded eight mutex symbols for eight guard symbols and gained nothing.pthread_mutex_lock/pthread_mutex_unlock(both libc) remain as mutex symbols.