Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
261210d
A few fixes in the threadpool semaphore. Unify Windows/Unix implement…
VSadov Feb 2, 2026
2a07e58
Apply suggestions from code review
VSadov Feb 3, 2026
cc8092a
Apply suggestions from code review
VSadov Feb 3, 2026
4b92449
comment
VSadov Feb 3, 2026
dc1a4e6
Typo: counted current thread as active twice.
VSadov Feb 3, 2026
afd874d
limit acceptable values of ThreadPool_UnfairSemaphoreSpinLimit
VSadov Feb 3, 2026
1630e9f
fix OSX and FreeBSD builds.
VSadov Feb 3, 2026
a8c5ab9
suppress unused-parameter in placeholders
VSadov Feb 3, 2026
62a3d0b
suppress missing-noreturn in placeholders
VSadov Feb 3, 2026
e5b0f28
futex timeout is relative by default
VSadov Feb 4, 2026
8a52a38
relaxed an assert.
VSadov Feb 4, 2026
b7c44fb
fix Linux x86 build
VSadov Feb 4, 2026
8f70ad7
Added assert in ~LowLevelThreadBlocker() as in other similar finalizers.
VSadov Feb 12, 2026
134381f
assert that _pendingSignals is not too large.
VSadov Feb 12, 2026
73af91f
idempotent Dispose
VSadov Feb 12, 2026
5853342
Update src/libraries/System.Private.CoreLib/src/System/Threading/LowL…
VSadov Feb 12, 2026
9b78e16
some more PR feedback
VSadov Feb 12, 2026
1a74f00
more feedback
VSadov Feb 12, 2026
4be373d
PR feedback (Mincore)
VSadov Feb 12, 2026
c49c135
add Synchronization.lib to native libs
VSadov Feb 12, 2026
3994f31
Unused Usings
VSadov Feb 12, 2026
bbfae6d
avoid extremely unlikely int overflow
VSadov Feb 13, 2026
793ec96
Add Synchronization.lib to NAOT repro project.
VSadov Feb 13, 2026
dd61a6b
separate the new manually introduced entries in WindowsAPI.txt
VSadov Feb 13, 2026
13d7a70
remove SuppressGCTransition on waking APIs
VSadov Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions docs/coding-guidelines/interop-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal static partial class Interop
...
internal static partial class Interop
{
internal static partial class mincore { ... }
internal static partial class Mincore { ... }
}
```
- With few exceptions, the only methods that should be defined in these interop types are DllImports.
Expand All @@ -49,7 +49,9 @@ internal static partial class Interop
```
\Common\src\Interop
\Windows
\mincore
\Kernel32
... interop files
\Mincore
... interop files
\Unix
\libc
Expand All @@ -71,8 +73,8 @@ As shown above, platforms may be additive, in that an assembly may use functiona
\libc
\Interop.strerror.cs
\Windows
\mincore
\Interop.OutputDebugString.cs
\Mincore
\Interop.WaitOnAddress.cs <-- Also contains WakeByAddressSingle
```

- If structs/constants will be used on their own without an associated DllImport, or if they may be used with multiple DllImports not in the same file, they should be declared in a separate file.
Expand All @@ -81,9 +83,9 @@ As shown above, platforms may be additive, in that an assembly may use functiona
```
\Common\src\Interop
\Windows
\mincore
\Interop.DuplicateHandle_SafeTokenHandle.cs
\Interop.DuplicateHandle_IntPtr.cs
\Kernel32
\Interop.DuplicateHandle_SafeFileHandle.cs
\Interop.DuplicateHandle_SafePipeHandle.cs
```

- The library names used per-platform are stored in internal constants in the Interop class in a private Libraries class in a per-platform file named Interop.Libraries.cs. These constants are then used for all DllImports to that library, rather than having the string duplicated each time, e.g.
Expand All @@ -94,12 +96,9 @@ internal static partial class Interop // contents of Common\src\Interop\Windows\
private static class Libraries
{
internal const string Kernel32 = "kernel32.dll";
internal const string OleAut32 = "oleaut32.dll";
internal const string Localization = "api-ms-win-core-localization-l1-2-0.dll";
internal const string Handle = "api-ms-win-core-handle-l1-1-0.dll";
internal const string ProcessThreads = "api-ms-win-core-processthreads-l1-1-0.dll";
internal const string File = "api-ms-win-core-file-l1-1-0.dll";
internal const string NamedPipe = "api-ms-win-core-namedpipe-l1-1-0.dll";
internal const string IO = "api-ms-win-core-io-l1-1-0.dll";
internal const string Synch = "api-ms-win-core-synch-l1-2-0.dll";
...
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<SdkNativeLibrary Include="user32.lib" />
<SdkNativeLibrary Include="version.lib" />
<SdkNativeLibrary Include="ws2_32.lib" />
<SdkNativeLibrary Include="Synchronization.lib" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/nativeaot/BuildIntegration/WindowsAPIs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2340,6 +2340,12 @@ ws2_32!WSCUpdateProvider
ws2_32!WSCWriteNameSpaceOrder
ws2_32!WSCWriteProviderOrder

#
# Synchronization.lib
#
api-ms-win-core-synch-l1-2-0!WaitOnAddress
api-ms-win-core-synch-l1-2-0!WakeByAddressSingle

#
# Include all memory allocation APIs from ucrt to ensure that all of them use allocator
# from the same ucrt copy. Also include the frequently used basic memory manipulation APIs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<NativeAotSourceRoot>$(CoreClrSourceRoot)nativeaot\</NativeAotSourceRoot>
<ArtifactsRoot>$(CoreClrSourceRoot)..\..\artifacts\</ArtifactsRoot>
<NativeRoot>$(CoreClrSourceRoot)..\native\</NativeRoot>
<Win32SDKLibs>advapi32.lib;bcrypt.lib;crypt32.lib;iphlpapi.lib;kernel32.lib;mswsock.lib;ncrypt.lib;normaliz.lib;ntdll.lib;ole32.lib;oleaut32.lib;secur32.lib;user32.lib;version.lib;ws2_32.lib</Win32SDKLibs>
<Win32SDKLibs>advapi32.lib;bcrypt.lib;crypt32.lib;iphlpapi.lib;kernel32.lib;mswsock.lib;ncrypt.lib;normaliz.lib;ntdll.lib;ole32.lib;oleaut32.lib;secur32.lib;user32.lib;version.lib;ws2_32.lib;Synchronization.lib</Win32SDKLibs>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelFutex_WaitOnAddress")]
internal static unsafe partial void LowLevelFutex_WaitOnAddress(int* address, int comparand);

[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelFutex_WaitOnAddressTimeout")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool LowLevelFutex_WaitOnAddressTimeout(int* address, int comparand, int timeoutMilliseconds);

[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelFutex_WakeByAddressSingle")]
internal static unsafe partial void LowLevelFutex_WakeByAddressSingle(int* address);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal static partial class Sys
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelMonitor_Acquire")]
internal static partial void LowLevelMonitor_Acquire(IntPtr monitor);

[SuppressGCTransition]
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelMonitor_Release")]
internal static partial void LowLevelMonitor_Release(IntPtr monitor);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal static partial class Libraries
internal const string Xolehlp = "xolehlp.dll";
internal const string Comdlg32 = "comdlg32.dll";
internal const string Gdiplus = "gdiplus.dll";
internal const string Oleaut32 = "oleaut32.dll";
internal const string Winspool = "winspool.drv";
internal const string Synch = "api-ms-win-core-synch-l1-2-0.dll";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal struct CRITICAL_SECTION
[LibraryImport(Libraries.Kernel32)]
internal static unsafe partial void EnterCriticalSection(CRITICAL_SECTION* lpCriticalSection);

[SuppressGCTransition]
[LibraryImport(Libraries.Kernel32)]
internal static unsafe partial void LeaveCriticalSection(CRITICAL_SECTION* lpCriticalSection);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Mincore
{
[LibraryImport(Libraries.Synch, SetLastError = true)]
internal static unsafe partial BOOL WaitOnAddress(void* Address, void* CompareAddress, nint AddressSize, int dwMilliseconds);

[LibraryImport(Libraries.Synch)]
internal static unsafe partial void WakeByAddressSingle(void* Address);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1840,6 +1840,9 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateSymbolicLink.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.CreateSymbolicLink.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Mincore\Interop.WaitOnAddress.cs">
<Link>Common\Interop\Windows\Mincore\Interop.WaitOnAddress.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CriticalSection.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.CriticalSection.cs</Link>
</Compile>
Expand Down Expand Up @@ -2471,6 +2474,9 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.LowLevelMonitor.cs">
<Link>Common\Interop\Unix\System.Native\Interop.LowLevelMonitor.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Futex.cs">
<Link>Common\Interop\Unix\System.Native\Interop.Futex.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.LSeek.cs">
<Link>Common\Interop\Unix\System.Native\Interop.LSeek.cs</Link>
</Compile>
Expand Down Expand Up @@ -2825,8 +2831,9 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or ('$(TargetsBrowser)' == 'true' and '$(FeatureWasmManagedThreads)' != 'true') or '$(TargetsWasi)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphore.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphore.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelLifoSemaphore.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelThreadBlocker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelFutex.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\LowLevelFutex.Unix.cs" Condition="'$(TargetsUnix)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Unix.cs" Condition="'$(TargetsUnix)' == 'true' or ('$(TargetsBrowser)' == 'true' and '$(FeatureWasmManagedThreads)' == 'true')" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Portable.cs" Condition="('$(TargetsBrowser)' != 'true' and '$(TargetsWasi)' != 'true') or '$(FeatureWasmManagedThreads)' == 'true'" />
Expand Down Expand Up @@ -2930,4 +2937,4 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_0.IPoll.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_0.PollInterop.cs" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,27 @@ internal static class Backoff
// the exponential backoff will generally be not more than 2X worse than the perfect guess and
// will do a lot less attempts than a simple retry. On multiprocessor machine fruitless attempts
// will cause unnecessary sharing of the contended state which may make modifying the state more expensive.
// To protect against degenerate cases we will cap the per-iteration wait to a few thousand spinwaits.
private const uint MaxExponentialBackoffBits = 14;
// To protect against degenerate cases we will cap the per-iteration wait to 1-2 thousand spinwaits.
private const uint MaxExponentialBackoffBits = 10;

internal static unsafe void Exponential(uint attempt)
internal static unsafe int Exponential(uint attempt)
{
attempt = Math.Min(attempt, MaxExponentialBackoffBits);
// We will backoff for some random number of spins that roughly grows as attempt^2
// No need for much randomness here, randomness is "good to have", we could do without it,
// so we will just cheaply hash in the stack location.
uint rand = (uint)&attempt * 2654435769u;
// Set the highmost bit to ensure minimum number of spins is exponentially increasing.
// It basically guarantees that we spin at least 0, 1, 2, 4, 8, 16, times, and so on
rand |= (1u << 31);
uint spins = rand >> (byte)(32 - attempt);
Thread.SpinWait((int)spins);
if (attempt > 0)
{
attempt = Math.Min(attempt, MaxExponentialBackoffBits);
// We will backoff for some random number of spins that roughly grows as attempt^2
// No need for much randomness here, randomness is "good to have", we could do without it,
// so we will just cheaply hash in the stack location.
uint rand = (uint)&attempt * 2654435769u;
// Set the highmost bit to ensure minimum number of spins is exponentially increasing.
// It basically guarantees that we spin at least 0, 1, 2, 4, 8, 16, times, and so on
rand |= (1u << 31);
uint spins = rand >> (byte)(32 - attempt);
Thread.SpinWait((int)spins);
return (int)spins;
}

return 0;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;


namespace System.Threading
{
// NOTE: Only supported on Linux for now.
// Most OS have support for futex-like APIs and should be added in the future.
// (ex: OSX has `os_sync_wait_on_address`, but support may vary by OS version)

/// <summary>
/// A compare-and-wait synchronization primitive.
/// Provides simple functionality to block and wake threads.
/// </summary>
internal static unsafe class LowLevelFutex
{
internal static void WaitOnAddress(int* address, int comparand)
{
Interop.Sys.LowLevelFutex_WaitOnAddress(address, comparand);
}

internal static bool WaitOnAddressTimeout(int* address, int comparand, int milliseconds)
{
Debug.Assert(milliseconds >= -1);
if (milliseconds == -1)
{
WaitOnAddress(address, comparand);
return true;
}

return Interop.Sys.LowLevelFutex_WaitOnAddressTimeout(address, comparand, milliseconds);
}

internal static void WakeByAddressSingle(int* address)
{
Interop.Sys.LowLevelFutex_WakeByAddressSingle(address);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System.Threading
{
/// <summary>
/// A compare-and-wait synchronization primitive.
/// Provides simple functionality to block and wake threads.
/// </summary>
internal static unsafe class LowLevelFutex
{
internal static void WaitOnAddress(int* address, int comparand)
{
Interop.BOOL result = Interop.Mincore.WaitOnAddress(address, &comparand, sizeof(int), -1);
// assert success, but in release treat unexpected results as spurious wakes
Debug.Assert(result == Interop.BOOL.TRUE);
}

internal static bool WaitOnAddressTimeout(int* address, int comparand, int milliseconds)
{
Interop.BOOL result = Interop.Mincore.WaitOnAddress(address, &comparand, sizeof(int), milliseconds);
if (result == Interop.BOOL.TRUE)
{
// normal or spurious wake
return true;
}

int lastError = Marshal.GetLastWin32Error();
Debug.Assert(lastError == Interop.Errors.ERROR_TIMEOUT);
if (lastError == Interop.Errors.ERROR_TIMEOUT)
{
// timeout
return false;
}

// in release treat unexpected results as spurious wakes
return true;
}

internal static void WakeByAddressSingle(int* address)
{
Interop.Mincore.WakeByAddressSingle(address);
}
}
}

This file was deleted.

Loading