Skip to content

Remove CoreCLR PAL semaphore support - #132636

Open
jkoritzinsky wants to merge 7 commits into
mainfrom
remove-coreclr-pal-semaphores
Open

Remove CoreCLR PAL semaphore support#132636
jkoritzinsky wants to merge 7 commits into
mainfrom
remove-coreclr-pal-semaphores

Conversation

@jkoritzinsky

@jkoritzinsky jkoritzinsky commented Aug 21, 2026

Copy link
Copy Markdown
Member

CoreCLR no longer needs PAL semaphore objects: Unix System.Threading.Semaphore uses the managed wait subsystem, and metadata's legacy UTSemReadWrite was the remaining PAL consumer. Removing this support reduces the PAL synchronization surface and its associated object-management code.

This change:

  • adds a minipal read-write lock backed by SRWLOCK on Windows and pthread_rwlock_t elsewhere, requesting writer preference where supported;
  • replaces metadata's UTSemReadWrite usage while preserving lock contracts, debugger bookkeeping, ownership transfers, and debug assertions; and
  • removes the PAL semaphore API, object implementation, exports, compatibility constants, and dedicated tests.

The POSIX semaphores used by debugger startup coordination in process.cpp remain; they are independent of PAL semaphore objects.

Testing

  • ./build.sh clr+libs+host
  • ./build.sh clr.runtime+clr.paltests
  • ./build.sh clr.runtime -rc Release
  • PAL test suite: 267 passed, 0 failed
  • multithreaded minipal read-write lock contention stress test
  • Reflection.Emit metadata-lock smoke test under the rebuilt Debug runtime

Performance

BenchmarkDotNet comparison using baseline and changed Release CoreRun hosts, with 2 launches, 5 warmup iterations, and 15 measurement iterations of 250 ms:

Scenario Baseline Changed Difference
Read metadata 1.974 us 1.965 us -0.5%
Write metadata 267.846 us 259.448 us -3.1%
Benchmark source
using System.Reflection;
using System.Reflection.Emit;
using BenchmarkDotNet.Attributes;

[MemoryDiagnoser]
public class MetadataLockBench
{
    private Assembly _assembly = null!;
    private int _typeId;

    [GlobalSetup]
    public void Setup()
    {
        AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(
            new AssemblyName("MetadataReadBenchmark"),
            AssemblyBuilderAccess.Run);
        ModuleBuilder module = assembly.DefineDynamicModule("MetadataReadBenchmark");

        for (int i = 0; i < 100; i++)
        {
            TypeBuilder type = module.DefineType($"Type{i}", TypeAttributes.Public);
            type.DefineField("Value", typeof(int), FieldAttributes.Public);
            type.CreateType();
        }

        _assembly = assembly;
    }

    [Benchmark]
    public Type[] ReadMetadata()
    {
        return _assembly.GetTypes();
    }

    [Benchmark]
    public Type WriteMetadata()
    {
        int typeId = Interlocked.Increment(ref _typeId);
        AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(
            new AssemblyName($"MetadataWriteBenchmark{typeId}"),
            AssemblyBuilderAccess.RunAndCollect);
        ModuleBuilder module = assembly.DefineDynamicModule("MetadataWriteBenchmark");
        TypeBuilder type = module.DefineType("EmittedType", TypeAttributes.Public);

        for (int i = 0; i < 10; i++)
        {
            type.DefineField($"Field{i}", typeof(int), FieldAttributes.Public);
        }

        return type.CreateType()!;
    }
}

Note

This pull request description was created by GitHub Copilot.

Add a minipal read-write lock and use it for metadata synchronization. Remove the PAL semaphore implementation, exports, and tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 21, 2026 18:02
@github-actions github-actions Bot added the area-PAL-coreclr only for closed issues label Aug 21, 2026
@azure-pipelines

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

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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.

Pull request overview

This PR removes CoreCLR PAL “semaphore object” support (implementation, exports, constants, and PAL tests) and replaces metadata’s legacy UTSemReadWrite usage with a new minipal_rwlock abstraction (SRWLOCK on Windows, pthread_rwlock_t elsewhere, preferring writers when supported). The intent is to shrink the PAL synchronization surface while preserving metadata locking contracts and debug bookkeeping.

Changes:

  • Add minipal_rwlock (new minipal rwlock header + implementation, plus configure-time feature detection and build wiring).
  • Migrate metadata locking from UTSemReadWrite to minipal_rwlock, including updated helper APIs/macros and debug write-lock tracking.
  • Remove PAL semaphore object support and associated PAL semaphore tests, lists, exports, and error constants.

Reviewed changes

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

Show a summary per file
File Description
src/native/minipal/rwlock.h Adds the minipal_rwlock public header (SRWLOCK/pthread-backed).
src/native/minipal/rwlock.c Implements rwlock init/enter/leave for Windows and pthread platforms.
src/native/minipal/minipalconfig.h.in Adds HAVE_PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP config define.
src/native/minipal/configure.cmake Adds compile-time probe for writer-preference rwlock attribute support.
src/native/minipal/CMakeLists.txt Includes rwlock.c in minipal sources.
src/coreclr/vm/gccover.cpp Removes now-unused utsem.h include.
src/coreclr/utilcode/utsem.cpp Deletes the legacy UTSemReadWrite implementation.
src/coreclr/utilcode/CMakeLists.txt Removes utsem.cpp from utilcode build sources.
src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOSemaphoreTest/WFSOSemaphoreTest.cpp Removes PAL semaphore WFSO test (PAL semaphore objects removed).
src/coreclr/pal/tests/palsuite/threading/releasesemaphore/test1/test.cpp Removes PAL ReleaseSemaphore test (PAL semaphore objects removed).
src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test10/test10.cpp Removes PAL DuplicateHandle(semaphore) test (PAL semaphore objects removed).
src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test3/createsemaphore.cpp Removes PAL CreateSemaphore/ReleaseSemaphore test (PAL semaphore objects removed).
src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CreateSemaphore.cpp Removes PAL CreateSemaphore/ReleaseSemaphore test (PAL semaphore objects removed).
src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.cpp Removes PAL CreateSemaphore/ReleaseSemaphore test (PAL semaphore objects removed).
src/coreclr/pal/tests/palsuite/paltestlist.txt Removes semaphore-related entries from PAL test list.
src/coreclr/pal/tests/palsuite/paltestlist_to_be_reviewed.txt Removes semaphore-related entry from “to be reviewed” list.
src/coreclr/pal/tests/palsuite/compilableTests.txt Removes semaphore-related entries from compilable tests list.
src/coreclr/pal/tests/palsuite/CMakeLists.txt Removes semaphore-related PAL test sources from build.
src/coreclr/pal/src/synchobj/semaphore.cpp Deletes PAL semaphore object implementation.
src/coreclr/pal/src/synchmgr/wait.cpp Removes semaphore from allowed wait/signalable PAL object types.
src/coreclr/pal/src/misc/errorstrings.cpp Removes semaphore-timeout error string mapping.
src/coreclr/pal/src/include/pal/semaphore.hpp Deletes PAL semaphore internal header.
src/coreclr/pal/src/include/pal/corunix.hpp Removes otiSemaphore PAL object type id.
src/coreclr/pal/src/CMakeLists.txt Removes semaphore object source from PAL build.
src/coreclr/pal/inc/pal.h Removes PAL semaphore APIs and semaphore access constants from public PAL header.
src/coreclr/pal/inc/pal_error.h Removes PAL-only semaphore-related error codes.
src/coreclr/md/runtime/mdinternalro.h Updates reader-writer lock type to minipal_rwlock* in MDInternalRO contract.
src/coreclr/md/inc/rwutil.h Introduces minipal_rwlock-based lock helpers and updates lock holder/macros.
src/coreclr/md/inc/metamodelrw.h Replaces debug lock pointer tracking with explicit “write-locked” flag tracking.
src/coreclr/md/inc/mdinternalrw.h Updates MDInternalRW reader-writer lock APIs to use minipal_rwlock*.
src/coreclr/md/enc/stdafx.h Removes utsem.h include from metadata ENC build.
src/coreclr/md/enc/rwutil.cpp Implements metadata lock helpers (CreateMDReadWriteLock, AcquireMD*Lock, etc.) and updates lock holder class.
src/coreclr/md/enc/metamodelrw.cpp Updates debug write-lock assertion to use the new flag-based tracking.
src/coreclr/md/enc/mdinternalrw.cpp Updates call sites to acquire/release metadata write lock using new helpers/types.
src/coreclr/md/compiler/stdafx.h Removes utsem.h include from metadata compiler build.
src/coreclr/md/compiler/regmeta.h Updates RegMeta lock type and lock wiring to minipal_rwlock*.
src/coreclr/md/compiler/regmeta.cpp Updates RegMeta lock allocation/teardown and lock usage to minipal_rwlock helpers.
src/coreclr/md/compiler/mdutil.h Updates global loaded-modules lock type to minipal_rwlock*.
src/coreclr/md/compiler/mdutil.cpp Updates global loaded-modules lock initialization and lock usage to minipal_rwlock.
src/coreclr/inc/winwrap.h Removes PAL-specific CreateSemaphore wrapper macro.
src/coreclr/inc/utsem.h Deletes legacy UTSemReadWrite header.
src/coreclr/inc/metadata.h Updates internal metadata interfaces to use minipal_rwlock* instead of UTSemReadWrite*.
src/coreclr/inc/corpriv.h Updates IMetaDataHelper lock APIs to use minipal_rwlock*.
src/coreclr/inc/corhost.h Removes local CreateSemaphore undef logic tied to PAL semaphore mapping.
src/coreclr/dlls/mscordac/mscordac_unixexports.src Removes PAL semaphore exports from DAC unix export list.

Comment thread src/native/minipal/configure.cmake Outdated
Comment thread src/coreclr/md/compiler/regmeta.cpp
Comment thread src/native/minipal/rwlock.c Outdated
Comment thread src/coreclr/md/enc/rwutil.cpp Outdated
Comment thread src/native/minipal/rwlock.c Outdated
Comment thread src/native/minipal/rwlock.c
Comment thread src/coreclr/md/inc/rwutil.h Outdated
Comment thread src/coreclr/md/enc/rwutil.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 21, 2026 19:12

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.

Pull request overview

Copilot reviewed 45 out of 45 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/md/enc/rwutil.cpp:1294

  • The comment block above CMDReadWriteLock::LockRead() says it obtains the "write lock", but this method acquires the read/shared lock. This is misleading when debugging lock-mode issues in metadata code.

This issue also appears on line 1318 of the same file.

// Used to obtain the write lock

src/coreclr/md/enc/rwutil.cpp:1318

  • The comment block above CMDReadWriteLock::LockWrite() says it obtains the "read lock", but this method acquires the write/exclusive lock. Keeping these comment headers accurate helps avoid confusion when tracing lock ownership and contract violations.
// Used to obtain the read lock

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 21, 2026 22:35

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.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/native/minipal/configure.cmake:34

  • The check for pthread_rwlockattr_setkind_np uses check_c_source_compiles(), which performs a link step. Because CMAKE_REQUIRED_LIBRARIES isn’t set to include the thread library, this probe can fail to link on platforms that require explicit pthread linkage (e.g., glibc), leaving HAVE_PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP unset even when the API is available.

Comment thread src/coreclr/utilcode/util.cpp Outdated
Copilot AI review requested due to automatic review settings August 22, 2026 16:48

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.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 1 comment.

Comment thread src/native/minipal/configure.cmake Outdated
@jkoritzinsky
jkoritzinsky requested a review from hoyosjs August 24, 2026 16:45
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 24, 2026 16:56

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.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/utilcode/util.cpp:920

  • InitializeGlobalSystemInfo uses a non-atomic check/set on g_fInitializedGlobalSystemInfo and writes into g_SystemInfo without synchronization. If GetTotalProcessorCount() is called concurrently in SELF_NO_HOST builds, this creates a data race and can allow readers to observe a partially written SYSTEM_INFO.

Consider using the same one-time-init pattern already used in this file for CPUGroupInfo::EnsureInitialized() (InterlockedCompareExchange + VolatileLoad/Store + wait) so initialization is thread-safe.

@jkoritzinsky

Copy link
Copy Markdown
Member Author

/azp run runtime-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 24, 2026 19:00

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.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/md/enc/rwutil.cpp:1294

  • The comment says this method obtains the write lock, but the implementation is CMDReadWriteLock::LockRead(). This looks like a copy/paste error and can confuse future maintainers when debugging lock usage.

This issue also appears on line 1318 of the same file.

// Used to obtain the write lock

src/coreclr/md/enc/rwutil.cpp:1318

  • The comment says this method obtains the read lock, but the implementation is CMDReadWriteLock::LockWrite(). This appears to be swapped with the LockRead comment above.
// Used to obtain the read lock


HRESULT CreateMDReadWriteLock(minipal_rwlock **ppLock)
{
minipal_rwlock *pLock = new (nothrow) minipal_rwlock;

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.

As long as we're here and it is partially done, should we update these functions with the correct contracts?

Comment thread src/coreclr/md/enc/rwutil.cpp Outdated
void CMDReadWriteLock::Debug_DetachMiniMd(CMiniMdRW *pMiniMd)
{
_ASSERTE(m_fLockedForWrite);
_ASSERTE(m_pMiniMd == pMiniMd);

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.

Why do we need an input argument here? I'm not sure what we're doing with it.

Comment thread src/coreclr/utilcode/util.cpp Outdated
Comment on lines +925 to +926
#endif // SELF_NO_HOST
#endif // HOST_WINDOWS

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.

Suggested change
#endif // SELF_NO_HOST
#endif // HOST_WINDOWS
#endif // SELF_NO_HOST && HOST_WINDOWS

Comment thread src/coreclr/utilcode/util.cpp Outdated
Comment on lines +908 to +909
#ifdef HOST_WINDOWS
#ifdef SELF_NO_HOST

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.

Suggested change
#ifdef HOST_WINDOWS
#ifdef SELF_NO_HOST
#if defined(HOST_WINDOWS) && defined(SELF_NO_HOST)

Copilot AI review requested due to automatic review settings August 25, 2026 20:26

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.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/native/minipal/rwlock.c:25

  • In the writer-preference pthread path, pthread_rwlockattr_setkind_np failing currently causes minipal_rwlock_init to fail (and metadata lock creation treats that as OOM). Since writer preference is an optimization, lock initialization should fall back to default pthread_rwlock_init(..., NULL) when the attribute API isn’t usable, rather than failing the lock entirely.

Comment on lines 264 to +268
// grab the write lock when we are creating the corresponding regmeta for the public interface
_ASSERTE( pInternalImport->GetReaderWriterLock() != NULL );
isLockedForWrite = true;
IfFailGo(pInternalImport->GetReaderWriterLock()->LockWrite());
IfFailGo(AcquireMDWriteLock(
pInternalImport->GetReaderWriterLock()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants