Skip to content

[Testing] Disable spin waiting when cpu quota limit is enabled #113184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ private static unsafe string[] InitializeCommandLineArgs(char* exePath, int argc
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Environment_GetProcessorCount")]
private static partial int GetProcessorCount();

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool GetIsCpuQuotaLimited();

// Used by VM
internal static string? GetResourceStringLocal(string key) => SR.GetResourceString(key);

Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/classlibnative/bcltype/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ extern "C" INT32 QCALLTYPE Environment_GetProcessorCount()
return processorCount;
}

FCIMPL0(FC_BOOL_RET, SystemNative::GetIsCpuQuotaLimited)
{
FCALL_CONTRACT;

FC_RETURN_BOOL(GetCurrentProcessIsCpuQuotaLimited());
}
FCIMPLEND

struct FindFailFastCallerStruct {
StackCrawlMark* pStackMark;
UINT_PTR retAddress;
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/classlibnative/bcltype/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class SystemNative
static FCDECL0(INT32, GetExitCode);

static FCDECL0(FC_BOOL_RET, IsServerGC);

static FCDECL0(FC_BOOL_RET, GetIsCpuQuotaLimited);
};

extern "C" void QCALLTYPE Environment_Exit(INT32 exitcode);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_SpinLimitProcFactor, W("SpinLimitProcFactor"),
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_SpinLimitConstant, W("SpinLimitConstant"), 0x0, "Hex value specifying the constant to add when calculating the maximum spin duration")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_SpinRetryCount, W("SpinRetryCount"), 0xA, "Hex value specifying the number of times the entire spin process is repeated (when applicable)")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_Monitor_SpinCount, W("Monitor_SpinCount"), 0x1e, "Hex value specifying the maximum number of spin iterations Monitor may perform upon contention on acquiring the lock before waiting.")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_Threading_ReduceSpinWaitingWhenCpuQuotaIsLimited, W("Threading_ReduceSpinWaitingWhenCpuQuotaIsLimited"), 1, "Reduces spin waiting when CPU quota is limited.")

///
/// Native Binder
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/inc/utilcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,8 @@ int GetTotalProcessorCount();
//******************************************************************************
int GetCurrentProcessCpuCount();

bool GetCurrentProcessIsCpuQuotaLimited();

uint32_t GetOsPageSize();


Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/nativeaot/Runtime/MiscHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,12 @@ FCIMPL0(int32_t, RhGetProcessCpuCount)
}
FCIMPLEND

FCIMPL0(FC_BOOL_RET, RhGetIsCpuQuotaLimited)
{
FC_RETURN_BOOL(PalGetIsCpuQuotaLimited());
}
FCIMPLEND

FCIMPL2(uint32_t, RhGetKnobValues, char *** pResultKeys, char *** pResultValues)
{
*pResultKeys = g_pRhConfig->GetKnobNames();
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/nativeaot/Runtime/PalRedhawk.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ typedef enum _EXCEPTION_DISPOSITION {
#endif // !DACCESS_COMPILE

extern uint32_t g_RhNumberOfProcessors;
extern bool g_RhIsCpuQuotaLimited;

#ifdef TARGET_UNIX
#define REDHAWK_PALIMPORT extern "C"
Expand Down Expand Up @@ -227,6 +228,7 @@ REDHAWK_PALIMPORT void REDHAWK_PALAPI PopulateControlSegmentRegisters(CONTEXT *
#endif

REDHAWK_PALIMPORT int32_t REDHAWK_PALAPI PalGetProcessCpuCount();
REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalGetIsCpuQuotaLimited();

// Retrieves the entire range of memory dedicated to the calling thread's stack. This does
// not get the current dynamic bounds of the stack, which can be significantly smaller than
Expand Down
17 changes: 15 additions & 2 deletions src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,14 @@ void InitializeCurrentProcessCpuCount()
count = GCToOSInterface::GetTotalProcessorCount();
#endif // HAVE_SCHED_GETAFFINITY

uint32_t cpuLimit;
double cpuLimit;
if (GetCpuLimit(&cpuLimit) && cpuLimit < count)
count = cpuLimit;
{
g_RhIsCpuQuotaLimited = true;

// Round up to the next integer
count = (uint32_t)(cpuLimit + 0.999999999);
}
}

_ASSERTE(count > 0);
Expand Down Expand Up @@ -1117,13 +1122,21 @@ extern "C" int32_t _stricmp(const char *string1, const char *string2)
}

uint32_t g_RhNumberOfProcessors;
bool g_RhIsCpuQuotaLimited = false;

REDHAWK_PALEXPORT int32_t PalGetProcessCpuCount()
{
ASSERT(g_RhNumberOfProcessors > 0);
return g_RhNumberOfProcessors;
}

REDHAWK_PALEXPORT bool PalGetIsCpuQuotaLimited()
{
// The number of processors should have been set by the time this is called
ASSERT(g_RhNumberOfProcessors > 0);
return g_RhIsCpuQuotaLimited;
}

__thread void* pStackHighOut = NULL;
__thread void* pStackLowOut = NULL;

Expand Down
30 changes: 13 additions & 17 deletions src/coreclr/nativeaot/Runtime/unix/cgroupcpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class CGroup
free(s_cpu_cgroup_path);
}

static bool GetCpuLimit(uint32_t *val)
static bool GetCpuLimit(double *val)
{
if (s_cgroup_version == 0)
return false;
Expand Down Expand Up @@ -345,7 +345,7 @@ class CGroup
return cgroup_path;
}

static bool GetCGroup1CpuLimit(uint32_t *val)
static bool GetCGroup1CpuLimit(double *val)
{
long long quota;
long long period;
Expand All @@ -358,12 +358,10 @@ class CGroup
if (period <= 0)
return false;

ComputeCpuLimit(period, quota, val);

return true;
return ComputeCpuLimit(period, quota, val);
}

static bool GetCGroup2CpuLimit(uint32_t *val)
static bool GetCGroup2CpuLimit(double *val)
{
char *filename = nullptr;
FILE *file = nullptr;
Expand Down Expand Up @@ -422,8 +420,7 @@ class CGroup
if (period_string == endptr || errno != 0)
goto done;

ComputeCpuLimit(period, quota, val);
result = true;
result = ComputeCpuLimit(period, quota, val);

done:
if (file)
Expand All @@ -434,18 +431,17 @@ class CGroup
return result;
}

static void ComputeCpuLimit(long long period, long long quota, uint32_t *val)
static bool ComputeCpuLimit(long long period, long long quota, double *val)
{
// Cannot have less than 1 CPU
if (quota <= period)
if (quota <= 0 || period <= 0)
{
*val = 1;
return;
// A CPU limit is not in effect, see https://www.kernel.org/doc/html/v5.9/scheduler/sched-bwc.html
return false;
}

// Calculate cpu count based on quota and round it up
double cpu_count = (double) quota / period + 0.999999999;
*val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX;
// Calculate cpu count based on quota
double cpu_count = (double) quota / period;
return true;
}

static long long ReadCpuCGroupValue(const char* subsystemFilename){
Expand Down Expand Up @@ -503,7 +499,7 @@ void InitializeCpuCGroup()
CGroup::Initialize();
}

bool GetCpuLimit(uint32_t* val)
bool GetCpuLimit(double* val)
{
if (val == nullptr)
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/nativeaot/Runtime/unix/cgroupcpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
#define __CGROUPCPU_H__

void InitializeCpuCGroup();
bool GetCpuLimit(uint32_t* val);
bool GetCpuLimit(double* val);

#endif // __CGROUPCPU_H__
8 changes: 8 additions & 0 deletions src/coreclr/nativeaot/Runtime/windows/PalRedhawkCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,21 @@ REDHAWK_PALEXPORT void REDHAWK_PALAPI PalGetModuleBounds(HANDLE hOsHandle, _Out_
}

uint32_t g_RhNumberOfProcessors;
bool g_RhIsCpuQuotaLimited = false;

REDHAWK_PALEXPORT int32_t REDHAWK_PALAPI PalGetProcessCpuCount()
{
ASSERT(g_RhNumberOfProcessors > 0);
return g_RhNumberOfProcessors;
}

REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalGetIsCpuQuotaLimited()
{
// The number of processors should have been set by the time this is called
ASSERT(g_RhNumberOfProcessors > 0);
return g_RhIsCpuQuotaLimited;
}

// Retrieves the entire range of memory dedicated to the calling thread's stack. This does
// not get the current dynamic bounds of the stack, which can be significantly smaller than
// the maximum bounds.
Expand Down
10 changes: 8 additions & 2 deletions src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,15 @@ void InitializeCurrentProcessCpuCount()

if (0 < maxRate && maxRate < MAXIMUM_CPU_RATE)
{
DWORD cpuLimit = (maxRate * GCToOSInterface::GetTotalProcessorCount() + MAXIMUM_CPU_RATE - 1) / MAXIMUM_CPU_RATE;
double cpuLimit = (double) (maxRate * GCToOSInterface::GetTotalProcessorCount()) / MAXIMUM_CPU_RATE;

if (cpuLimit < count)
count = cpuLimit;
{
g_RhIsCpuQuotaLimited = true;

// Round cpu limit up to the next integer
count = (DWORD)(cpuLimit + 0.999999999);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ internal static void FailFast(string? message, Exception? exception, string erro

private static int GetProcessorCount() => Runtime.RuntimeImports.RhGetProcessCpuCount();

private static bool GetIsCpuQuotaLimited() => Runtime.RuntimeImports.RhGetIsCpuQuotaLimited();

internal static void ShutdownCore()
{
#if !TARGET_BROWSER // WASMTODO Be careful what happens here as if the code has called emscripten_set_main_loop then the main loop method will normally be called repeatedly after this method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ internal struct GCHeapHardLimitInfo
[RuntimeImport(RuntimeLibrary, "RhGetProcessCpuCount")]
internal static extern int RhGetProcessCpuCount();

[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhGetIsCpuQuotaLimited")]
internal static extern bool RhGetIsCpuQuotaLimited();

//
// calls for GCHandle.
// These methods are needed to implement GCHandle class like functionality (optional)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/pal/inc/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -2570,7 +2570,7 @@ PAL_GetTotalCpuCount();
PALIMPORT
BOOL
PALAPI
PAL_GetCpuLimit(UINT* val);
PAL_GetCpuLimit(double* val);

typedef BOOL(*UnwindReadMemoryCallback)(PVOID address, PVOID buffer, SIZE_T size);

Expand Down
30 changes: 13 additions & 17 deletions src/coreclr/pal/src/misc/cgroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class CGroup
free(s_cpu_cgroup_path);
}

static bool GetCpuLimit(UINT *val)
static bool GetCpuLimit(double *val)
{
if (s_cgroup_version == 0)
return false;
Expand Down Expand Up @@ -347,7 +347,7 @@ class CGroup
return cgroup_path;
}

static bool GetCGroup1CpuLimit(UINT *val)
static bool GetCGroup1CpuLimit(double *val)
{
long long quota;
long long period;
Expand All @@ -360,12 +360,10 @@ class CGroup
if (period <= 0)
return false;

ComputeCpuLimit(period, quota, val);

return true;
return ComputeCpuLimit(period, quota, val);
}

static bool GetCGroup2CpuLimit(UINT *val)
static bool GetCGroup2CpuLimit(double *val)
{
char *filename = nullptr;
FILE *file = nullptr;
Expand Down Expand Up @@ -424,8 +422,7 @@ class CGroup
if (period_string == endptr || errno != 0)
goto done;

ComputeCpuLimit(period, quota, val);
result = true;
result = ComputeCpuLimit(period, quota, val);

done:
if (file)
Expand All @@ -436,18 +433,17 @@ class CGroup
return result;
}

static void ComputeCpuLimit(long long period, long long quota, uint32_t *val)
static bool ComputeCpuLimit(long long period, long long quota, double *val)
{
// Cannot have less than 1 CPU
if (quota <= period)
if (quota <= 0 || period <= 0)
{
*val = 1;
return;
// A CPU limit is not in effect, see https://www.kernel.org/doc/html/v5.9/scheduler/sched-bwc.html
return false;
}

// Calculate cpu count based on quota and round it up
double cpu_count = (double) quota / period + 0.999999999;
*val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX;
// Calculate cpu count based on quota
*val = (double) quota / period;
return true;
}

static long long ReadCpuCGroupValue(const char* subsystemFilename){
Expand Down Expand Up @@ -515,7 +511,7 @@ void CleanupCGroup()

BOOL
PALAPI
PAL_GetCpuLimit(UINT* val)
PAL_GetCpuLimit(double* val)
{
if (val == nullptr)
return FALSE;
Expand Down
Loading
Loading