Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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_SuppressWakePreemption")]
internal static unsafe partial int SuppressWakePreemption(int* previousPolicy, int* previousPriority);

[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_RestoreWakePreemption")]
internal static partial int RestoreWakePreemption(int previousPolicy, int previousPriority);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2468,6 +2468,9 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Futex.cs" Condition="'$(TargetsLinux)' == 'true'">
<Link>Common\Interop\Unix\System.Native\Interop.Futex.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.WakePreemption.cs">
<Link>Common\Interop\Unix\System.Native\Interop.WakePreemption.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetCpuUtilization.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetCpuUtilization.cs</Link>
</Compile>
Expand Down Expand Up @@ -2905,6 +2908,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.Unix.cs" Condition="'$(TargetsWindows)' != 'true' and '$(TargetsBrowser)' != '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.WakePreemption.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Portable.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\RegisteredWaitHandle.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Threading
{
internal sealed partial class LowLevelLifoSemaphore
{
private readonly struct WakePreemptionScope

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.

@copilot these are threadpool threads. The user is not expected to change varioaus parameters like scheduler or priority from defaults. Even if user does so, it would be ok and perhaps even a mildly good thing if suppressing/restoring would revert thread state to defaults.

I do not think we need to capture and revert to the original state. We should revert to default.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Simplified as requested. RestoreWakePreemption now unconditionally sets SCHED_OTHER (priority 0) instead of capturing and restoring the previous state. The native API is now parameterless — suppress sets SCHED_BATCH, restore sets SCHED_OTHER. WakePreemptionScope on Linux carries only a bool Suppressed to guard the restore call. Commit: b7de4e3.

{
#if TARGET_LINUX
Comment thread
VSadov marked this conversation as resolved.
internal readonly int PreviousPolicy;
internal readonly int PreviousPriority;

internal WakePreemptionScope(int previousPolicy, int previousPriority)
{
PreviousPolicy = previousPolicy;
PreviousPriority = previousPriority;
}
#endif
}

private static WakePreemptionScope SuppressWakePreemption()
{
#if TARGET_WINDOWS
// GetCurrentThread() returns a pseudo-handle (-2) that is valid
// only on the calling thread and does not need to be closed.
Interop.Kernel32.SetThreadPriorityBoost(Interop.Kernel32.GetCurrentThread(), bDisablePriorityBoost: true);
return default;
#elif TARGET_LINUX
Comment thread
VSadov marked this conversation as resolved.
int previousPolicy = -1;
int previousPriority = 0;
unsafe
{
Interop.Sys.SuppressWakePreemption(&previousPolicy, &previousPriority);
}

return new WakePreemptionScope(previousPolicy, previousPriority);
#else
return default;
#endif
}

private static void RestoreWakePreemption(WakePreemptionScope scope)
{
#if TARGET_WINDOWS
Interop.Kernel32.SetThreadPriorityBoost(Interop.Kernel32.GetCurrentThread(), bDisablePriorityBoost: false);
#elif TARGET_LINUX
Comment thread
VSadov marked this conversation as resolved.
if (scope.PreviousPolicy != -1)
{
Interop.Sys.RestoreWakePreemption(scope.PreviousPolicy, scope.PreviousPriority);
}
#else
_ = scope;
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -320,15 +320,14 @@ private bool Block(int timeoutMs)

if (blockerNode != null)
{
#if TARGET_WINDOWS
// Disable the priority boost that Windows would normally apply
// Suppress the wake-up preemption that the OS would normally apply
// when this thread is unblocked. The semaphore is used to park workers.
// A transient priority boost on wake provides no benefit here and can
// result in woken thread preempting already working threads.
// GetCurrentThread() returns a pseudo-handle (-2) that is valid
// only on the calling thread and does not need to be closed.
Interop.Kernel32.SetThreadPriorityBoost(Interop.Kernel32.GetCurrentThread(), bDisablePriorityBoost: true);
#endif
// A transient wake-up advantage provides no benefit here and can result in
// the woken thread preempting already-working threads. On Windows this
// disables SetThreadPriorityBoost. On Linux this temporarily switches the
// thread to SCHED_BATCH, which sched(7) documents as applying "a small
// scheduling penalty with respect to wakeup behavior".
WakePreemptionScope wakePreemptionScope = SuppressWakePreemption();

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.

These are threadpool threads under control of the runtime. We do not need to optimize/anticipate cases where these syscalls do not work since not working is both unexpected and benign.

@copilot Add an assert to make sure that the syscalls work in common/typical scenarios.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added Debug.Assert(result == 0, ...) after each call to SuppressWakePreemption and RestoreWakePreemption in LowLevelLifoSemaphore.WakePreemption.cs. These will fire in debug builds if either syscall unexpectedly fails. Commit: 6ed4b5d.


try
{
Expand All @@ -349,10 +348,7 @@ private bool Block(int timeoutMs)
}
finally
{
#if TARGET_WINDOWS
// restore the default.
Interop.Kernel32.SetThreadPriorityBoost(Interop.Kernel32.GetCurrentThread(), bDisablePriorityBoost: false);
#endif
RestoreWakePreemption(wakePreemptionScope);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/native/libs/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_LowLevelFutex_WaitOnAddressTimeout)
DllImportEntry(SystemNative_LowLevelFutex_WakeByAddressSingle)
#endif
DllImportEntry(SystemNative_SuppressWakePreemption)
DllImportEntry(SystemNative_RestoreWakePreemption)
Comment thread
VSadov marked this conversation as resolved.
DllImportEntry(SystemNative_LoadLibrary)
DllImportEntry(SystemNative_GetLoadLibraryError)
DllImportEntry(SystemNative_GetProcAddress)
Expand Down
61 changes: 61 additions & 0 deletions src/native/libs/System.Native/pal_threading.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#pragma clang diagnostic ignored "-Wjump-misses-init"
#endif

#define WAKE_PREEMPTION_PREVIOUS_POLICY_UNSUPPORTED (-1)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// LowLevelMonitor - Represents a non-recursive mutex and condition

Expand Down Expand Up @@ -297,6 +299,65 @@ void SystemNative_LowLevelFutex_WakeByAddressSingle(int32_t* address)

#endif // defined(TARGET_LINUX)

int32_t SystemNative_SuppressWakePreemption(int32_t* previousPolicy, int32_t* previousPriority)
{
assert(previousPolicy != NULL);
assert(previousPriority != NULL);

*previousPolicy = WAKE_PREEMPTION_PREVIOUS_POLICY_UNSUPPORTED;
*previousPriority = 0;

#if defined(TARGET_LINUX)
Comment thread
VSadov marked this conversation as resolved.
struct sched_param previousParameters;

int previousPolicyLocal = sched_getscheduler(0);
if (previousPolicyLocal == -1)
{
return errno;
}

if (sched_getparam(0, &previousParameters) != 0)
{
return errno;
}

struct sched_param suppressedParameters;
suppressedParameters.sched_priority = 0;

if (sched_setscheduler(0, SCHED_BATCH, &suppressedParameters) != 0)
{
return errno;
}

*previousPolicy = previousPolicyLocal;
*previousPriority = previousParameters.sched_priority;
#endif

return 0;
}

int32_t SystemNative_RestoreWakePreemption(int32_t previousPolicy, int32_t previousPriority)
{
if (previousPolicy == WAKE_PREEMPTION_PREVIOUS_POLICY_UNSUPPORTED)
{
return 0;
}

#if defined(TARGET_LINUX)
Comment thread
VSadov marked this conversation as resolved.
struct sched_param previousParameters;
previousParameters.sched_priority = previousPriority;

if (sched_setscheduler(0, previousPolicy, &previousParameters) != 0)
{
return errno;
}
#else
(void)previousPriority; // unused
#endif

return 0;
}

int32_t SystemNative_CreateThread(uintptr_t stackSize, void *(*startAddress)(void*), void *parameter)
{
bool result = false;
Expand Down
4 changes: 4 additions & 0 deletions src/native/libs/System.Native/pal_threading.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ PALEXPORT int32_t SystemNative_LowLevelFutex_WaitOnAddressTimeout(int32_t* addre

PALEXPORT void SystemNative_LowLevelFutex_WakeByAddressSingle(int32_t* address);

PALEXPORT int32_t SystemNative_SuppressWakePreemption(int32_t* previousPolicy, int32_t* previousPriority);

PALEXPORT int32_t SystemNative_RestoreWakePreemption(int32_t previousPolicy, int32_t previousPriority);

PALEXPORT int32_t SystemNative_CreateThread(uintptr_t stackSize, void *(*startAddress)(void*), void *parameter);

PALEXPORT int32_t SystemNative_SchedGetCpu(void);
Expand Down
Loading