From d43960366d75fae2518bc58b938e00ac8037f2b6 Mon Sep 17 00:00:00 2001 From: Shrikant Jadhav Date: Wed, 11 Mar 2026 15:13:04 +0530 Subject: [PATCH 1/4] Win API returns invalid args for 0 limit, fixed that case --- .../devdoc/job_object_helper_requirements.md | 42 +-- win32/src/job_object_helper.c | 234 +++++++++----- .../job_object_helper_int/CMakeLists.txt | 1 + .../job_object_helper_create_int.c | 21 +- .../job_object_helper_memory_int.c | 23 +- .../job_object_helper_multi_process_int.c | 20 +- .../job_object_helper_negative_int.c | 160 ++++++++-- .../job_object_helper_partial_limits_int.c | 295 ++++++++++++++++++ .../job_object_helper_reconfigure_int.c | 23 +- .../job_object_helper_ut.c | 280 ++++++++--------- 10 files changed, 795 insertions(+), 304 deletions(-) create mode 100644 win32/tests/job_object_helper_int/job_object_helper_partial_limits_int.c diff --git a/win32/devdoc/job_object_helper_requirements.md b/win32/devdoc/job_object_helper_requirements.md index 8617cc38..053cf892 100644 --- a/win32/devdoc/job_object_helper_requirements.md +++ b/win32/devdoc/job_object_helper_requirements.md @@ -9,10 +9,6 @@ Note: `job_object_helper_set_job_limits_to_current_process` and `job_object_help ## Exposed API ```c -// Passing JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL as percent_cpu disables CPU rate control (removes the throttle) -#define JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL 0 -// Passing JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT as percent_physical_memory removes memory limits -#define JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT 0 typedef struct JOB_OBJECT_HELPER_TAG JOB_OBJECT_HELPER; THANDLE_TYPE_DECLARE(JOB_OBJECT_HELPER); @@ -35,16 +31,18 @@ static void job_object_helper_dispose(JOB_OBJECT_HELPER* job_object_helper); ```c static int internal_job_object_helper_set_cpu_limit(HANDLE job_object, uint32_t percent_cpu); ``` -`internal_job_object_helper_set_cpu_limit` is an internal function that sets or disables the CPU rate control on the given job object by calling the Windows `SetInformationJobObject` API. +`internal_job_object_helper_set_cpu_limit` is an internal function that sets the CPU rate control on the given job object by calling the Windows `SetInformationJobObject` API. -**SRS_JOB_OBJECT_HELPER_88_004: [** If `percent_cpu` is `JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL`, `internal_job_object_helper_set_cpu_limit` shall call `SetInformationJobObject` passing `JobObjectCpuRateControlInformation` with `ControlFlags` set to `0` to disable CPU rate control. **]** +**SRS_JOB_OBJECT_HELPER_88_004: [** If `percent_cpu` is `0` or greater than `100`, `internal_job_object_helper_set_cpu_limit` shall fail and return a non-zero value. **]** -**SRS_JOB_OBJECT_HELPER_88_005: [** If `percent_cpu` is not `JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL`, `internal_job_object_helper_set_cpu_limit` shall set `ControlFlags` to `JOB_OBJECT_CPU_RATE_CONTROL_ENABLE` and `JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP`, and `CpuRate` to `percent_cpu` times `100`. **]** +**SRS_JOB_OBJECT_HELPER_88_005: [** `internal_job_object_helper_set_cpu_limit` shall set `ControlFlags` to `JOB_OBJECT_CPU_RATE_CONTROL_ENABLE` and `JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP`, and `CpuRate` to `percent_cpu` times `100`. **]** **SRS_JOB_OBJECT_HELPER_88_006: [** `internal_job_object_helper_set_cpu_limit` shall call `SetInformationJobObject` passing `JobObjectCpuRateControlInformation` and the `JOBOBJECT_CPU_RATE_CONTROL_INFORMATION`. **]** **SRS_JOB_OBJECT_HELPER_88_007: [** If `SetInformationJobObject` fails, `internal_job_object_helper_set_cpu_limit` shall fail and return a non-zero value. **]** +**SRS_JOB_OBJECT_HELPER_88_042: [** On success, `internal_job_object_helper_set_cpu_limit` shall store `percent_cpu` in the singleton state. **]** + **SRS_JOB_OBJECT_HELPER_88_008: [** `internal_job_object_helper_set_cpu_limit` shall succeed and return `0`. **]** @@ -52,16 +50,18 @@ static int internal_job_object_helper_set_cpu_limit(HANDLE job_object, uint32_t ```c static int internal_job_object_helper_set_memory_limit(HANDLE job_object, uint32_t percent_physical_memory); ``` -`internal_job_object_helper_set_memory_limit` is an internal function that sets or removes the memory limit on the given job object by calling the Windows `SetInformationJobObject` API. +`internal_job_object_helper_set_memory_limit` is an internal function that sets the memory limit on the given job object by calling the Windows `SetInformationJobObject` API. -**SRS_JOB_OBJECT_HELPER_88_010: [** If `percent_physical_memory` is `JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT`, `internal_job_object_helper_set_memory_limit` shall call `SetInformationJobObject` passing `JobObjectExtendedLimitInformation` with `LimitFlags` set to `0` to remove memory limits. **]** +**SRS_JOB_OBJECT_HELPER_88_010: [** If `percent_physical_memory` is `0` or greater than `100`, `internal_job_object_helper_set_memory_limit` shall fail and return a non-zero value. **]** -**SRS_JOB_OBJECT_HELPER_88_011: [** If `percent_physical_memory` is not `JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT`, `internal_job_object_helper_set_memory_limit` shall call `GlobalMemoryStatusEx` to get the total amount of physical memory. **]** +**SRS_JOB_OBJECT_HELPER_88_011: [** `internal_job_object_helper_set_memory_limit` shall call `GlobalMemoryStatusEx` to get the total amount of physical memory. **]** -**SRS_JOB_OBJECT_HELPER_88_012: [** If `percent_physical_memory` is not `JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT`, `internal_job_object_helper_set_memory_limit` shall set `JobMemoryLimit` and `ProcessMemoryLimit` to `percent_physical_memory` percent of the physical memory and call `SetInformationJobObject` with `JobObjectExtendedLimitInformation`. **]** +**SRS_JOB_OBJECT_HELPER_88_012: [** `internal_job_object_helper_set_memory_limit` shall set `JobMemoryLimit` and `ProcessMemoryLimit` to `percent_physical_memory` percent of the physical memory and call `SetInformationJobObject` with `JobObjectExtendedLimitInformation`. **]** **SRS_JOB_OBJECT_HELPER_88_013: [** If there are any failures, `internal_job_object_helper_set_memory_limit` shall fail and return a non-zero value. **]** +**SRS_JOB_OBJECT_HELPER_88_043: [** On success, `internal_job_object_helper_set_memory_limit` shall store `percent_physical_memory` in the singleton state. **]** + **SRS_JOB_OBJECT_HELPER_88_014: [** `internal_job_object_helper_set_memory_limit` shall succeed and return `0`. **]** @@ -71,9 +71,9 @@ static int internal_job_object_helper_reconfigure(uint32_t percent_cpu, uint32_t ``` `internal_job_object_helper_reconfigure` is an internal function that reconfigures the existing process-level singleton job object with new CPU and memory limits. -**SRS_JOB_OBJECT_HELPER_88_003: [** `internal_job_object_helper_reconfigure` shall call `internal_job_object_helper_set_cpu_limit` to apply the CPU rate control to the Windows job object. **]** +**SRS_JOB_OBJECT_HELPER_88_003: [** If `percent_cpu` is not `0`, `internal_job_object_helper_reconfigure` shall call `internal_job_object_helper_set_cpu_limit` to apply the CPU rate control to the Windows job object. **]** -**SRS_JOB_OBJECT_HELPER_88_009: [** `internal_job_object_helper_reconfigure` shall call `internal_job_object_helper_set_memory_limit` to apply the memory limit to the Windows job object. **]** +**SRS_JOB_OBJECT_HELPER_88_009: [** If `percent_physical_memory` is not `0`, `internal_job_object_helper_reconfigure` shall call `internal_job_object_helper_set_memory_limit` to apply the memory limit to the Windows job object. **]** **SRS_JOB_OBJECT_HELPER_88_017: [** If there are any failures, `internal_job_object_helper_reconfigure` shall fail and return a non-zero value. **]** @@ -90,9 +90,9 @@ static int internal_job_object_helper_create(const char* job_name, uint32_t perc **SRS_JOB_OBJECT_HELPER_88_036: [** `internal_job_object_helper_create` shall call `CreateJobObjectA` passing `job_name` for `lpName` and `NULL` for `lpJobAttributes`. **]** -**SRS_JOB_OBJECT_HELPER_88_037: [** `internal_job_object_helper_create` shall call `internal_job_object_helper_set_cpu_limit` to apply the CPU rate control to the Windows job object. **]** +**SRS_JOB_OBJECT_HELPER_88_037: [** If `percent_cpu` is not `0`, `internal_job_object_helper_create` shall call `internal_job_object_helper_set_cpu_limit` to apply the CPU rate control to the Windows job object. **]** -**SRS_JOB_OBJECT_HELPER_88_038: [** `internal_job_object_helper_create` shall call `internal_job_object_helper_set_memory_limit` to apply the memory limit to the Windows job object. **]** +**SRS_JOB_OBJECT_HELPER_88_038: [** If `percent_physical_memory` is not `0`, `internal_job_object_helper_create` shall call `internal_job_object_helper_set_memory_limit` to apply the memory limit to the Windows job object. **]** **SRS_JOB_OBJECT_HELPER_88_039: [** `internal_job_object_helper_create` shall call `GetCurrentProcess` to get the current process handle. **]** @@ -111,20 +111,22 @@ MOCKABLE_FUNCTION(, THANDLE(JOB_OBJECT_HELPER), job_object_helper_set_job_limits ``` `job_object_helper_set_job_limits_to_current_process` creates the Job Object with limits if not present and assigns the current process to it. Returns a THANDLE to allow reuse of the job object across multiple processes. If being used only in single process, then handle can be released immediately and process continues having the set limits. -The function implements a process-level singleton pattern to prevent Job Object accumulation. On the first call, it creates the job object. On subsequent calls, the existing job object's limits are applied in-place via `SetInformationJobObject` and the existing singleton is returned. CPU rate control can be disabled by passing `JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL` and memory limits can be removed by passing `JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT`. +The function implements a process-level singleton pattern to prevent Job Object accumulation. On the first call, it creates the job object. On subsequent calls, the existing job object's limits are applied in-place via `SetInformationJobObject` and the existing singleton is returned. Passing `0` for both `percent_cpu` and `percent_physical_memory` is not allowed and will return `NULL`. During reconfiguration, a value of `0` is not allowed for a parameter that was previously set to a non-zero value. **SRS_JOB_OBJECT_HELPER_19_001: [** If `percent_cpu` is greater than `100` then `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** **SRS_JOB_OBJECT_HELPER_88_034: [** If `percent_physical_memory` is greater than `100` then `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** +**SRS_JOB_OBJECT_HELPER_88_040: [** If `percent_cpu` is `0` and `percent_physical_memory` is `0` then `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** + **SRS_JOB_OBJECT_HELPER_88_030: [** If `job_object_singleton_state.job_object_helper` is not `NULL`, `job_object_helper_set_job_limits_to_current_process` shall not create a new job object. **]** +**SRS_JOB_OBJECT_HELPER_88_041: [** During reconfiguration, if `percent_cpu` is `0` and the `job_object_singleton_state.percent_cpu` is non-zero, or if `percent_physical_memory` is `0` and the `job_object_singleton_state.percent_memory` is non-zero, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** + **SRS_JOB_OBJECT_HELPER_88_002: [** If `job_object_singleton_state.job_object_helper` is not `NULL`, `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_reconfigure` to apply the limits to the existing job object. **]** **SRS_JOB_OBJECT_HELPER_88_021: [** If `internal_job_object_helper_reconfigure` returns `0`, `job_object_helper_set_job_limits_to_current_process` shall increment the reference count on the existing `THANDLE(JOB_OBJECT_HELPER)` and return it. **]** -**SRS_JOB_OBJECT_HELPER_88_022: [** If `job_object_singleton_state.job_object_helper` is `NULL` and `percent_cpu` is `JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL` and `percent_physical_memory` is `JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT`, `job_object_helper_set_job_limits_to_current_process` shall return `NULL` without creating a job object. **]** - **SRS_JOB_OBJECT_HELPER_88_023: [** If `job_object_singleton_state.job_object_helper` is `NULL` and both `percent_cpu` and `percent_physical_memory` are `100`, `job_object_helper_set_job_limits_to_current_process` shall return `NULL` without creating a job object. **]** **SRS_JOB_OBJECT_HELPER_88_031: [** `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_create` to create a new job object and assign it to the current process. **]** @@ -141,3 +143,7 @@ MOCKABLE_FUNCTION(, void, job_object_helper_deinit_for_test); `job_object_helper_deinit_for_test` releases the process-level singleton, allowing a fresh job object to be created on the next call. This is intended for test cleanup and should not be used in production code, as calling it would allow creating new job objects that compound with the existing one. **SRS_JOB_OBJECT_HELPER_88_027: [** `job_object_helper_deinit_for_test` shall release the singleton `THANDLE(JOB_OBJECT_HELPER)` by assigning it to `NULL`. **]** + +**SRS_JOB_OBJECT_HELPER_88_044: [** `job_object_helper_deinit_for_test` shall reset `percent_cpu` to 0 in the singleton state. **]** + +**SRS_JOB_OBJECT_HELPER_88_045: [** `job_object_helper_deinit_for_test` shall reset `percent_memory` to 0 in the singleton state. **]** diff --git a/win32/src/job_object_helper.c b/win32/src/job_object_helper.c index 21d87dcc..b0010067 100644 --- a/win32/src/job_object_helper.c +++ b/win32/src/job_object_helper.c @@ -36,6 +36,8 @@ THANDLE_TYPE_DEFINE(JOB_OBJECT_HELPER); typedef struct JOB_OBJECT_SINGLETON_STATE_TAG { THANDLE(JOB_OBJECT_HELPER) job_object_helper; + uint32_t percent_cpu; + uint32_t percent_memory; } JOB_OBJECT_SINGLETON_STATE; static JOB_OBJECT_SINGLETON_STATE job_object_singleton_state = { NULL }; @@ -53,30 +55,35 @@ static int internal_job_object_helper_set_cpu_limit(HANDLE job_object, uint32_t { int result; - JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_rate_control_information = { 0 }; - if (percent_cpu == JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL) + if(percent_cpu == 0 || percent_cpu > MAX_CPU_PERCENT) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_004: [ If percent_cpu is JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL, internal_job_object_helper_set_cpu_limit shall call SetInformationJobObject passing JobObjectCpuRateControlInformation with ControlFlags set to 0 to disable CPU rate control. ]*/ - cpu_rate_control_information.ControlFlags = 0; + /*Codes_SRS_JOB_OBJECT_HELPER_88_004: [ If percent_cpu is 0 or greater than 100, internal_job_object_helper_set_cpu_limit shall fail and return a non-zero value. ]*/ + LogError("Invalid argument: percent_cpu=%" PRIu32 "", percent_cpu); + result = MU_FAILURE; } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_005: [ If percent_cpu is not JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL, internal_job_object_helper_set_cpu_limit shall set ControlFlags to JOB_OBJECT_CPU_RATE_CONTROL_ENABLE and JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, and CpuRate to percent_cpu times 100. ]*/ + JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_rate_control_information = { 0 }; + + /*Codes_SRS_JOB_OBJECT_HELPER_88_005: [ internal_job_object_helper_set_cpu_limit shall set ControlFlags to JOB_OBJECT_CPU_RATE_CONTROL_ENABLE and JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, and CpuRate to percent_cpu times 100. ]*/ cpu_rate_control_information.ControlFlags = JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP; cpu_rate_control_information.CpuRate = percent_cpu * 100; - } - /*Codes_SRS_JOB_OBJECT_HELPER_88_006: [ internal_job_object_helper_set_cpu_limit shall call SetInformationJobObject passing JobObjectCpuRateControlInformation and the JOBOBJECT_CPU_RATE_CONTROL_INFORMATION. ]*/ - if (!SetInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_rate_control_information, sizeof(cpu_rate_control_information))) - { - /*Codes_SRS_JOB_OBJECT_HELPER_88_007: [ If SetInformationJobObject fails, internal_job_object_helper_set_cpu_limit shall fail and return a non-zero value. ]*/ - LogLastError("failure in SetInformationJobObject(job_object=%p, JobObjectCpuRateControlInformation, &cpu_rate_control_information=%p, sizeof(cpu_rate_control_information)=%zu)", - job_object, &cpu_rate_control_information, sizeof(cpu_rate_control_information)); - result = MU_FAILURE; - } - else - { - /*Codes_SRS_JOB_OBJECT_HELPER_88_008: [ internal_job_object_helper_set_cpu_limit shall succeed and return 0. ]*/ - result = 0; + + /*Codes_SRS_JOB_OBJECT_HELPER_88_006: [ internal_job_object_helper_set_cpu_limit shall call SetInformationJobObject passing JobObjectCpuRateControlInformation and the JOBOBJECT_CPU_RATE_CONTROL_INFORMATION. ]*/ + if (!SetInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_rate_control_information, sizeof(cpu_rate_control_information))) + { + LogLastError("failure in SetInformationJobObject(job_object=%p, JobObjectCpuRateControlInformation, &cpu_rate_control_information=%p, sizeof(cpu_rate_control_information)=%zu)", + job_object, &cpu_rate_control_information, sizeof(cpu_rate_control_information)); + /*Codes_SRS_JOB_OBJECT_HELPER_88_007: [ If SetInformationJobObject fails, internal_job_object_helper_set_cpu_limit shall fail and return a non-zero value. ]*/ + result = MU_FAILURE; + } + else + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_042: [ On success, internal_job_object_helper_set_cpu_limit shall store percent_cpu in the singleton state. ]*/ + job_object_singleton_state.percent_cpu = percent_cpu; + /*Codes_SRS_JOB_OBJECT_HELPER_88_008: [ internal_job_object_helper_set_cpu_limit shall succeed and return 0. ]*/ + result = 0; + } } return result; @@ -87,25 +94,16 @@ static int internal_job_object_helper_set_memory_limit(HANDLE job_object, uint32 { int result; - if (percent_physical_memory == JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT) + if(percent_physical_memory == 0 || percent_physical_memory > MAX_MEMORY_PERCENT) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_010: [ If percent_physical_memory is JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, internal_job_object_helper_set_memory_limit shall call SetInformationJobObject passing JobObjectExtendedLimitInformation with LimitFlags set to 0 to remove memory limits. ]*/ - JOBOBJECT_EXTENDED_LIMIT_INFORMATION extended_limit_information = { 0 }; - if (!SetInformationJobObject(job_object, JobObjectExtendedLimitInformation, &extended_limit_information, sizeof(extended_limit_information))) - { - /*Codes_SRS_JOB_OBJECT_HELPER_88_013: [ If there are any failures, internal_job_object_helper_set_memory_limit shall fail and return a non-zero value. ]*/ - LogLastError("failure in SetInformationJobObject to remove memory limits (job_object=%p)", job_object); - result = MU_FAILURE; - } - else - { - /*Codes_SRS_JOB_OBJECT_HELPER_88_014: [ internal_job_object_helper_set_memory_limit shall succeed and return 0. ]*/ - result = 0; - } + /*Codes_SRS_JOB_OBJECT_HELPER_88_010: [ If percent_physical_memory is 0 or greater than 100, internal_job_object_helper_set_memory_limit shall fail and return a non-zero value. ]*/ + LogError("Invalid argument: percent_physical_memory=%" PRIu32 "", percent_physical_memory); + result = MU_FAILURE; } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_011: [ If percent_physical_memory is not JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, internal_job_object_helper_set_memory_limit shall call GlobalMemoryStatusEx to get the total amount of physical memory. ]*/ + + /*Codes_SRS_JOB_OBJECT_HELPER_88_011: [ internal_job_object_helper_set_memory_limit shall call GlobalMemoryStatusEx to get the total amount of physical memory. ]*/ MEMORYSTATUSEX memory_status_ex; memory_status_ex.dwLength = sizeof(memory_status_ex); if (!GlobalMemoryStatusEx(&memory_status_ex)) @@ -116,7 +114,7 @@ static int internal_job_object_helper_set_memory_limit(HANDLE job_object, uint32 } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_012: [ If percent_physical_memory is not JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, internal_job_object_helper_set_memory_limit shall set JobMemoryLimit and ProcessMemoryLimit to percent_physical_memory percent of the physical memory and call SetInformationJobObject with JobObjectExtendedLimitInformation. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_012: [ internal_job_object_helper_set_memory_limit shall set JobMemoryLimit and ProcessMemoryLimit to percent_physical_memory percent of the physical memory and call SetInformationJobObject with JobObjectExtendedLimitInformation. ]*/ JOBOBJECT_EXTENDED_LIMIT_INFORMATION extended_limit_information = { 0 }; extended_limit_information.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY | JOB_OBJECT_LIMIT_PROCESS_MEMORY; SIZE_T memory_limit = percent_physical_memory * memory_status_ex.ullTotalPhys / 100; @@ -131,6 +129,8 @@ static int internal_job_object_helper_set_memory_limit(HANDLE job_object, uint32 } else { + /*Codes_SRS_JOB_OBJECT_HELPER_88_043: [ On success, internal_job_object_helper_set_memory_limit shall store percent_physical_memory in the singleton state. ]*/ + job_object_singleton_state.percent_memory = percent_physical_memory; /*Codes_SRS_JOB_OBJECT_HELPER_88_014: [ internal_job_object_helper_set_memory_limit shall succeed and return 0. ]*/ result = 0; } @@ -147,31 +147,65 @@ static int internal_job_object_helper_reconfigure(uint32_t percent_cpu, uint32_t { int result; - /*Codes_SRS_JOB_OBJECT_HELPER_88_003: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ - if (internal_job_object_helper_set_cpu_limit(job_object_singleton_state.job_object_helper->job_object, percent_cpu) != 0) + bool failed = false; + /*Codes_SRS_JOB_OBJECT_HELPER_88_003: [ If percent_cpu is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ + if(percent_cpu != 0) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ - LogError("failure in internal_job_object_helper_set_cpu_limit(job_object=%p, percent_cpu=%" PRIu32 ") during reconfiguration", - job_object_singleton_state.job_object_helper->job_object, percent_cpu); - result = MU_FAILURE; + if (internal_job_object_helper_set_cpu_limit(job_object_singleton_state.job_object_helper->job_object, percent_cpu) != 0) + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ + LogError("failure in internal_job_object_helper_set_cpu_limit(job_object=%p, percent_cpu=%" PRIu32 ") during reconfiguration", + job_object_singleton_state.job_object_helper->job_object, percent_cpu); + failed = true; + } + else + { + /* Do Nothing */ + } + } + else + { + /* Do Nothing */ + } + + if (failed) + { + /* Error already logged */ } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_009: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ - if (internal_job_object_helper_set_memory_limit(job_object_singleton_state.job_object_helper->job_object, percent_physical_memory) != 0) + /*Codes_SRS_JOB_OBJECT_HELPER_88_009: [ If percent_physical_memory is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ + if(percent_physical_memory != 0) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ - LogError("failure in internal_job_object_helper_set_memory_limit(job_object=%p, percent_physical_memory=%" PRIu32 ") during reconfiguration", - job_object_singleton_state.job_object_helper->job_object, percent_physical_memory); - result = MU_FAILURE; + if (internal_job_object_helper_set_memory_limit(job_object_singleton_state.job_object_helper->job_object, percent_physical_memory) != 0) + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ + LogError("failure in internal_job_object_helper_set_memory_limit(job_object=%p, percent_physical_memory=%" PRIu32 ") during reconfiguration", + job_object_singleton_state.job_object_helper->job_object, percent_physical_memory); + failed = true; + } + else + { + /* Do Nothing */ + } } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ - result = 0; + /* Do Nothing */ } } + if (failed) + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ + result = MU_FAILURE; + } + else + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ + result = 0; + } + return result; } @@ -199,38 +233,70 @@ static int internal_job_object_helper_create(const char* job_name, uint32_t perc } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_037: [ internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ - if (internal_job_object_helper_set_cpu_limit(job_object_helper->job_object, percent_cpu) != 0) + bool failed = false; + /*Codes_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ + if(percent_cpu != 0) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ - } - else - { - /*Codes_SRS_JOB_OBJECT_HELPER_88_038: [ internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ - if (internal_job_object_helper_set_memory_limit(job_object_helper->job_object, percent_physical_memory) != 0) + if (internal_job_object_helper_set_cpu_limit(job_object_helper->job_object, percent_cpu) != 0) { /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ + failed = true; } - else + } + else + { + /* Do Nothing*/ + } + + if(failed) + { + /* Error already logged */ + } + else + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ + if(percent_physical_memory != 0) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_039: [ internal_job_object_helper_create shall call GetCurrentProcess to get the current process handle. ]*/ - HANDLE current_process = GetCurrentProcess(); - /*Codes_SRS_JOB_OBJECT_HELPER_19_008: [ internal_job_object_helper_create shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ - if (!AssignProcessToJobObject(job_object_helper->job_object, current_process)) + if (internal_job_object_helper_set_memory_limit(job_object_helper->job_object, percent_physical_memory) != 0) { /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ - LogLastError("failure in AssignProcessToJobObject(job_object=%p, current_process=%p)", job_object_helper->job_object, current_process); + failed = true; } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_024: [ On success, internal_job_object_helper_create shall store the THANDLE(JOB_OBJECT_HELPER) in the process-level singleton state. ]*/ - THANDLE_INITIALIZE_MOVE(JOB_OBJECT_HELPER)(&job_object_singleton_state.job_object_helper, &job_object_helper); - /*Codes_SRS_JOB_OBJECT_HELPER_88_033: [ internal_job_object_helper_create shall succeed and return 0. ]*/ - result = 0; - goto all_ok; + /* Do Nothing */ } } + else + { + /* Do Nothing */ + } } + + if (failed) + { + /* Error already logged */ + } + else + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_039: [ internal_job_object_helper_create shall call GetCurrentProcess to get the current process handle. ]*/ + HANDLE current_process = GetCurrentProcess(); + /*Codes_SRS_JOB_OBJECT_HELPER_19_008: [ internal_job_object_helper_create shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ + if (!AssignProcessToJobObject(job_object_helper->job_object, current_process)) + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ + LogLastError("failure in AssignProcessToJobObject(job_object=%p, current_process=%p)", job_object_helper->job_object, current_process); + } + else + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_024: [ On success, internal_job_object_helper_create shall store the THANDLE(JOB_OBJECT_HELPER) in the process-level singleton state. ]*/ + THANDLE_INITIALIZE_MOVE(JOB_OBJECT_HELPER)(&job_object_singleton_state.job_object_helper, &job_object_helper); + /*Codes_SRS_JOB_OBJECT_HELPER_88_033: [ internal_job_object_helper_create shall succeed and return 0. ]*/ + result = 0; + goto all_ok; + } + } + if (!CloseHandle(job_object_helper->job_object)) { LogLastError("failure in CloseHandle(job_object_helper->job_object=%p)", job_object_helper->job_object); @@ -252,7 +318,9 @@ IMPLEMENT_MOCKABLE_FUNCTION(, THANDLE(JOB_OBJECT_HELPER), job_object_helper_set_ /*Codes_SRS_JOB_OBJECT_HELPER_19_001: [ If percent_cpu is greater than 100 then job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ percent_cpu > MAX_CPU_PERCENT || /*Codes_SRS_JOB_OBJECT_HELPER_88_034: [ If percent_physical_memory is greater than 100 then job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ - percent_physical_memory > MAX_MEMORY_PERCENT) + percent_physical_memory > MAX_MEMORY_PERCENT || + /*Codes_SRS_JOB_OBJECT_HELPER_88_040: [ If percent_cpu is 0 and percent_physical_memory is 0 then job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + (percent_cpu == 0 && percent_physical_memory == 0)) { LogError("Invalid arguments: job_name=%s, percent_cpu=%" PRIu32 ", percent_physical_memory=%" PRIu32 "", MU_P_OR_NULL(job_name), percent_cpu, percent_physical_memory); } @@ -263,23 +331,32 @@ IMPLEMENT_MOCKABLE_FUNCTION(, THANDLE(JOB_OBJECT_HELPER), job_object_helper_set_ { LogWarning("Reconfiguring existing process-level singleton Job Object (cpu: %" PRIu32 ", memory: %" PRIu32 ")", percent_cpu, percent_physical_memory); - /*Codes_SRS_JOB_OBJECT_HELPER_88_002: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_reconfigure to apply the limits to the existing job object. ]*/ - if (internal_job_object_helper_reconfigure(percent_cpu, percent_physical_memory) != 0) + + if(/*Codes_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + (percent_cpu == 0 && job_object_singleton_state.percent_cpu != 0) || + /*Codes_SRS_JOB_OBJECT_HELPER_88_042: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + (percent_physical_memory == 0 && job_object_singleton_state.percent_memory != 0)) { - /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ - /* Error already logged */ + LogError("Invalid arguments: percent_cpu or percent_physical_memory cannot be set to 0 once it has been set to a non-zero value. Received percent_cpu=%" PRIu32 ", received percent_physical_memory=%" PRIu32 ", current percent_cpu=%" PRIu32 ", current percent_physical_memory=%" PRIu32 "", percent_cpu, percent_physical_memory, job_object_singleton_state.percent_cpu, job_object_singleton_state.percent_memory); } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ - THANDLE_INITIALIZE(JOB_OBJECT_HELPER)(&result, job_object_singleton_state.job_object_helper); + /*Codes_SRS_JOB_OBJECT_HELPER_88_002: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_reconfigure to apply the limits to the existing job object. ]*/ + if (internal_job_object_helper_reconfigure(percent_cpu, percent_physical_memory) != 0) + { + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + /* Error already logged */ + } + else + { + /*Codes_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ + THANDLE_INITIALIZE(JOB_OBJECT_HELPER)(&result, job_object_singleton_state.job_object_helper); + } } } else { if ( - /*Codes_SRS_JOB_OBJECT_HELPER_88_022: [ If job_object_singleton_state.job_object_helper is NULL and percent_cpu is JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL and percent_physical_memory is JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, job_object_helper_set_job_limits_to_current_process shall return NULL without creating a job object. ]*/ - (percent_cpu == JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL && percent_physical_memory == JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT) || /*Codes_SRS_JOB_OBJECT_HELPER_88_023: [ If job_object_singleton_state.job_object_helper is NULL and both percent_cpu and percent_physical_memory are 100, job_object_helper_set_job_limits_to_current_process shall return NULL without creating a job object. ]*/ (percent_cpu == MAX_CPU_PERCENT && percent_physical_memory == MAX_MEMORY_PERCENT)) { @@ -310,4 +387,9 @@ IMPLEMENT_MOCKABLE_FUNCTION(, void, job_object_helper_deinit_for_test) LogWarning("job_object_helper_deinit_for_test called - this should only be used for test cleanup"); /*Codes_SRS_JOB_OBJECT_HELPER_88_027: [ job_object_helper_deinit_for_test shall release the singleton THANDLE(JOB_OBJECT_HELPER) by assigning it to NULL. ]*/ THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&job_object_singleton_state.job_object_helper, NULL); + + /*Codes_SRS_JOB_OBJECT_HELPER_88_044: [ job_object_helper_deinit_for_test shall reset percent_cpu to 0 in the singleton state. ]*/ + job_object_singleton_state.percent_cpu = 0; + /*Codes_SRS_JOB_OBJECT_HELPER_88_045: [ job_object_helper_deinit_for_test shall reset percent_memory to 0 in the singleton state. ]*/ + job_object_singleton_state.percent_memory = 0; } diff --git a/win32/tests/job_object_helper_int/CMakeLists.txt b/win32/tests/job_object_helper_int/CMakeLists.txt index 8f0a6933..cfd426e3 100644 --- a/win32/tests/job_object_helper_int/CMakeLists.txt +++ b/win32/tests/job_object_helper_int/CMakeLists.txt @@ -8,6 +8,7 @@ set(int_test_names job_object_helper_multi_process_int job_object_helper_singleton_int job_object_helper_reconfigure_int + job_object_helper_partial_limits_int ) # Tests that need the helper tester executable built first diff --git a/win32/tests/job_object_helper_int/job_object_helper_create_int.c b/win32/tests/job_object_helper_int/job_object_helper_create_int.c index 1f560f22..8de5ebbf 100644 --- a/win32/tests/job_object_helper_int/job_object_helper_create_int.c +++ b/win32/tests/job_object_helper_int/job_object_helper_create_int.c @@ -19,6 +19,18 @@ #define MEGABYTE ((size_t)1024 * 1024) #define TEST_JOB_NAME_PREFIX "job_test_ebs_" +static THANDLE(JOB_OBJECT_HELPER) create_job_object_with_limits(char* job_name_out, size_t job_name_size, uint32_t cpu, uint32_t memory) +{ + UUID_T job_name_uuid; + (void)uuid_produce(&job_name_uuid); + (void)snprintf(job_name_out, job_name_size, TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); + LogInfo("Creating job object (cpu=%" PRIu32 ", memory=%" PRIu32 ") with job name: %s...", cpu, memory, job_name_out); + + THANDLE(JOB_OBJECT_HELPER) result = job_object_helper_set_job_limits_to_current_process(job_name_out, cpu, memory); + ASSERT_IS_NOT_NULL(result, "Job object should be created (cpu=%" PRIu32 ", memory=%" PRIu32 ")", cpu, memory); + return result; +} + BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) TEST_SUITE_INITIALIZE(suite_init) @@ -46,15 +58,8 @@ TEST_FUNCTION(test_job_object_helper_set_job_limits_to_current_process) * 3. sets the job limits */ - UUID_T job_name_uuid; - (void)uuid_produce(&job_name_uuid); - char job_name[64]; - (void)snprintf(job_name, sizeof(job_name), TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); - LogInfo("Running test with job name: %s...", job_name); - - THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 50, 1); - ASSERT_IS_NOT_NULL(job_object_helper); + THANDLE(JOB_OBJECT_HELPER) job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), 50, 1); /* Check that the job object was created */ HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); diff --git a/win32/tests/job_object_helper_int/job_object_helper_memory_int.c b/win32/tests/job_object_helper_int/job_object_helper_memory_int.c index 60ca467d..22f5e4d9 100644 --- a/win32/tests/job_object_helper_int/job_object_helper_memory_int.c +++ b/win32/tests/job_object_helper_int/job_object_helper_memory_int.c @@ -20,6 +20,18 @@ #define NUM_ALLOCATE_MEMORY_BLOCKS 12 #define TEST_JOB_NAME_PREFIX "job_test_ebs_" +static THANDLE(JOB_OBJECT_HELPER) create_job_object_with_limits(char* job_name_out, size_t job_name_size, uint32_t cpu, uint32_t memory) +{ + UUID_T job_name_uuid; + (void)uuid_produce(&job_name_uuid); + (void)snprintf(job_name_out, job_name_size, TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); + LogInfo("Creating job object (cpu=%" PRIu32 ", memory=%" PRIu32 ") with job name: %s...", cpu, memory, job_name_out); + + THANDLE(JOB_OBJECT_HELPER) result = job_object_helper_set_job_limits_to_current_process(job_name_out, cpu, memory); + ASSERT_IS_NOT_NULL(result, "Job object should be created (cpu=%" PRIu32 ", memory=%" PRIu32 ")", cpu, memory); + return result; +} + BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) TEST_SUITE_INITIALIZE(suite_init) @@ -41,12 +53,9 @@ TEST_FUNCTION_CLEANUP(cleanup) TEST_FUNCTION(test_job_object_helper_set_job_limits_to_current_process_check_memory_limits) { - UUID_T job_name_uuid; - (void)uuid_produce(&job_name_uuid); - + /* Set the process's memory limit to 1% */ char job_name[64]; - (void)snprintf(job_name, sizeof(job_name), TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); - LogInfo("Running test with Job name: %s...", job_name); + THANDLE(JOB_OBJECT_HELPER) job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), 50, 1); /* Get the total available Memory */ MEMORYSTATUSEX mem_status; @@ -59,10 +68,6 @@ TEST_FUNCTION(test_job_object_helper_set_job_limits_to_current_process_check_mem LogInfo("Total Memory: %zu", total_memory); LogInfo("1%% of Total Memory: %zu", one_percent_of_total_memory); - /* Set the process's memory limit to 1% */ - THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 50, 1); - ASSERT_IS_NOT_NULL(job_object_helper); - /* allocations till 1% should pass */ char* buffer[NUM_ALLOCATE_MEMORY_BLOCKS]; uint64_t memory_size = one_percent_of_total_memory / 10; diff --git a/win32/tests/job_object_helper_int/job_object_helper_multi_process_int.c b/win32/tests/job_object_helper_int/job_object_helper_multi_process_int.c index 71c8b15e..c3082a48 100644 --- a/win32/tests/job_object_helper_int/job_object_helper_multi_process_int.c +++ b/win32/tests/job_object_helper_int/job_object_helper_multi_process_int.c @@ -21,6 +21,14 @@ #define NUM_RETRY 3 #define TEST_JOB_NAME_PREFIX "job_test_ebs_" +static void generate_test_job_name(char* job_name_out, size_t job_name_size) +{ + UUID_T job_name_uuid; + (void)uuid_produce(&job_name_uuid); + (void)snprintf(job_name_out, job_name_size, TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); + LogInfo("Test job name: %s", job_name_out); +} + static void get_job_object_helper_tester_excutable_path(char* full_path, size_t full_path_size) { /* Use GetModuleFileNameA to get the path of the current executable */ @@ -70,12 +78,8 @@ TEST_FUNCTION_CLEANUP(cleanup) TEST_FUNCTION(test_job_object_helper_set_job_limits_to_current_process_from_multiple_processes) { - UUID_T job_name_uuid; - (void)uuid_produce(&job_name_uuid); - char job_name[64]; - (void)snprintf(job_name, sizeof(job_name), TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); - LogInfo("Running test with job name: %s...", job_name); + generate_test_job_name(job_name, sizeof(job_name)); char full_path[MAX_PATH]; get_job_object_helper_tester_excutable_path(full_path, sizeof(full_path)); @@ -151,12 +155,8 @@ TEST_FUNCTION(test_job_object_helper_set_job_limits_to_current_process_from_mult TEST_FUNCTION(test_job_object_helper_set_job_limits_to_current_process_from_multiple_processes_memory_limit_validation) { - UUID_T job_name_uuid; - (void)uuid_produce(&job_name_uuid); - char job_name[64]; - (void)snprintf(job_name, sizeof(job_name), TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); - LogInfo("Running test with job name: %s...", job_name); + generate_test_job_name(job_name, sizeof(job_name)); char full_path[MAX_PATH]; get_job_object_helper_tester_excutable_path(full_path, sizeof(full_path)); diff --git a/win32/tests/job_object_helper_int/job_object_helper_negative_int.c b/win32/tests/job_object_helper_int/job_object_helper_negative_int.c index 7d323936..b7c3fc8b 100644 --- a/win32/tests/job_object_helper_int/job_object_helper_negative_int.c +++ b/win32/tests/job_object_helper_int/job_object_helper_negative_int.c @@ -20,6 +20,27 @@ #define MAX_MEMORY_PERCENT 100 #define TEST_JOB_NAME_PREFIX "job_test_ebs_" +#define INITIAL_CPU_PERCENT 50 +#define INITIAL_MEMORY_PERCENT 1 + +static void generate_test_job_name(char* job_name_out, size_t job_name_size) +{ + UUID_T job_name_uuid; + (void)uuid_produce(&job_name_uuid); + (void)snprintf(job_name_out, job_name_size, TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); + LogInfo("Test job name: %s", job_name_out); +} + +static THANDLE(JOB_OBJECT_HELPER) create_job_object_with_limits(char* job_name_out, size_t job_name_size, uint32_t cpu, uint32_t memory) +{ + generate_test_job_name(job_name_out, job_name_size); + LogInfo("Creating job object (cpu=%" PRIu32 ", memory=%" PRIu32 ") with job name: %s...", cpu, memory, job_name_out); + + THANDLE(JOB_OBJECT_HELPER) result = job_object_helper_set_job_limits_to_current_process(job_name_out, cpu, memory); + ASSERT_IS_NOT_NULL(result, "Job object should be created (cpu=%" PRIu32 ", memory=%" PRIu32 ")", cpu, memory); + return result; +} + BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) TEST_SUITE_INITIALIZE(suite_init) @@ -39,55 +60,140 @@ TEST_FUNCTION_CLEANUP(cleanup) job_object_helper_deinit_for_test(); } -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_no_effective_limits_and_null_job_name) +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_both_zero_and_null_job_name) +{ + /* (0,0) is not allowed — at least one limit must be non-zero. */ + + ///act + THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(NULL, 0, 0); + + ///assert + ASSERT_IS_NULL(job_object_helper, "(0, 0) should return NULL"); +} + +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_both_100_and_null_job_name) +{ + /* (100,100) is effectively no limits — the function returns NULL without creating a job object. */ + + ///act + THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(NULL, MAX_CPU_PERCENT, MAX_MEMORY_PERCENT); + + ///assert + ASSERT_IS_NULL(job_object_helper, "(100, 100) should return NULL (no job object needed)"); +} + +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_both_zero_and_named_job_object) +{ + /* (0,0) is not allowed. Uses a named job object to verify no OS object was created. */ + + ///arrange + char job_name[64]; + generate_test_job_name(job_name, sizeof(job_name)); + + ///act + THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 0, 0); + + ///assert + ASSERT_IS_NULL(job_object_helper, "(0, 0) should return NULL"); + + /* Verify that no job object was actually created */ + HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); + ASSERT_IS_NULL(job_object, "No job object should have been created for (0, 0)"); +} + +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_both_100_and_named_job_object) { - /* Both (0,0) and (100,100) are effectively no limits. - The function should return NULL without creating a job object, - avoiding any risk of compounding if called again. - Uses empty string (unnamed job object). */ + /* (100,100) is effectively no limits. Uses a named job object to verify no OS object was created. */ - uint32_t cpu_limits[2] = { 0, MAX_CPU_PERCENT }; - uint32_t memory_limits[2] = { 0, MAX_MEMORY_PERCENT }; + ///arrange + char job_name[64]; + generate_test_job_name(job_name, sizeof(job_name)); + + ///act + THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, MAX_CPU_PERCENT, MAX_MEMORY_PERCENT); + + ///assert + ASSERT_IS_NULL(job_object_helper, "(100, 100) should return NULL (no job object needed)"); + + /* Verify that no job object was actually created */ + HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); + ASSERT_IS_NULL(job_object, "No job object should have been created for (100, 100)"); +} + +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_cpu_exceeds_100) +{ + /* percent_cpu > 100 is invalid */ + + uint32_t cpu_limits[] = { 101, 200, UINT32_MAX }; + uint32_t memory_limits[] = { 50, 50, 50 }; for (int i = 0; i < MU_COUNT_ARRAY_ITEMS(cpu_limits); ++i) { + LogInfo("Running test with cpu=%" PRIu32 ", memory=%" PRIu32 "...", cpu_limits[i], memory_limits[i]); + ///act THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(NULL, cpu_limits[i], memory_limits[i]); ///assert - ASSERT_IS_NULL(job_object_helper, "(%" PRIu32 ", %" PRIu32 ") should return NULL (no job object needed)", cpu_limits[i], memory_limits[i]); + ASSERT_IS_NULL(job_object_helper, "(%" PRIu32 ", %" PRIu32 ") should return NULL", cpu_limits[i], memory_limits[i]); } } -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_no_effective_limits_and_named_job_object) +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_memory_exceeds_100) { - /* Both (0,0) and (100,100) are effectively no limits. - The function should return NULL and no job object should be created. - Uses a named job object so we can verify via OpenJobObjectA that no OS object was created. */ + /* percent_physical_memory > 100 is invalid */ - uint32_t cpu_limits[2] = { 0, MAX_CPU_PERCENT }; - uint32_t memory_limits[2] = { 0, MAX_MEMORY_PERCENT }; + uint32_t cpu_limits[] = { 50, 50, 50 }; + uint32_t memory_limits[] = { 101, 200, UINT32_MAX }; for (int i = 0; i < MU_COUNT_ARRAY_ITEMS(cpu_limits); ++i) { - ///arrange - UUID_T job_name_uuid; - (void)uuid_produce(&job_name_uuid); - - char job_name[64]; - (void)snprintf(job_name, sizeof(job_name), TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); - LogInfo("Running test with job name: %s, cpu=%" PRIu32 ", memory=%" PRIu32 "...", job_name, cpu_limits[i], memory_limits[i]); + LogInfo("Running test with cpu=%" PRIu32 ", memory=%" PRIu32 "...", cpu_limits[i], memory_limits[i]); ///act - THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, cpu_limits[i], memory_limits[i]); + THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process(NULL, cpu_limits[i], memory_limits[i]); ///assert - ASSERT_IS_NULL(job_object_helper, "(%" PRIu32 ", %" PRIu32 ") should return NULL (no job object needed)", cpu_limits[i], memory_limits[i]); - - /* Verify that no job object was actually created */ - HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); - ASSERT_IS_NULL(job_object, "No job object should have been created for (%" PRIu32 ", %" PRIu32 ")", cpu_limits[i], memory_limits[i]); + ASSERT_IS_NULL(job_object_helper, "(%" PRIu32 ", %" PRIu32 ") should return NULL", cpu_limits[i], memory_limits[i]); } } +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_reconfigure_cpu_from_nonzero_to_zero) +{ + /* Reconfiguring CPU from a non-zero value to 0 is not allowed. + Once CPU rate control has been applied, it cannot be removed — pass 100 to effectively disable. */ + + ///arrange + char job_name[64]; + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); + + ///act — try to set cpu to 0 (previously was non-zero) + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 0, INITIAL_MEMORY_PERCENT); + + ///assert + ASSERT_IS_NULL(reconfigured_job_object_helper, "Reconfigure cpu from non-zero to 0 should fail and return NULL"); + + ///cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); +} + +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_reconfigure_memory_from_nonzero_to_zero) +{ + /* Reconfiguring memory from a non-zero value to 0 is not allowed. + Once memory limits have been applied, they cannot be removed — pass 100 to effectively disable. */ + + ///arrange + char job_name[64]; + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); + + ///act — try to set memory to 0 (previously was non-zero) + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, INITIAL_CPU_PERCENT, 0); + + ///assert + ASSERT_IS_NULL(reconfigured_job_object_helper, "Reconfigure memory from non-zero to 0 should fail and return NULL"); + + ///cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); +} + END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) diff --git a/win32/tests/job_object_helper_int/job_object_helper_partial_limits_int.c b/win32/tests/job_object_helper_int/job_object_helper_partial_limits_int.c new file mode 100644 index 00000000..0e6c51d0 --- /dev/null +++ b/win32/tests/job_object_helper_int/job_object_helper_partial_limits_int.c @@ -0,0 +1,295 @@ +// Copyright(C) Microsoft Corporation.All rights reserved. + + +#include +#include + +#include "windows.h" + +#include "macro_utils/macro_utils.h" + +#include "testrunnerswitcher.h" + +#include "c_pal/gballoc_hl.h" +#include "c_pal/gballoc_hl_redirect.h" +#include "c_pal/job_object_helper.h" +#include "c_pal/uuid.h" + + +#define MEGABYTE ((size_t)1024 * 1024) +#define TEST_JOB_NAME_PREFIX "job_test_partial_" +#define TEST_CPU_PERCENT 50 +#define TEST_MEMORY_PERCENT 1 +#define INITIAL_CPU_PERCENT 50 +#define INITIAL_MEMORY_PERCENT 1 +#define RECONFIGURED_CPU_PERCENT 30 +#define RECONFIGURED_MEMORY_PERCENT 2 + +static THANDLE(JOB_OBJECT_HELPER) create_job_object_with_limits(char* job_name_out, size_t job_name_size, uint32_t cpu, uint32_t memory) +{ + UUID_T job_name_uuid; + (void)uuid_produce(&job_name_uuid); + (void)snprintf(job_name_out, job_name_size, TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); + LogInfo("Creating job object (cpu=%" PRIu32 ", memory=%" PRIu32 ") with job name: %s...", cpu, memory, job_name_out); + + THANDLE(JOB_OBJECT_HELPER) result = job_object_helper_set_job_limits_to_current_process(job_name_out, cpu, memory); + ASSERT_IS_NOT_NULL(result, "Job object should be created (cpu=%" PRIu32 ", memory=%" PRIu32 ")", cpu, memory); + return result; +} + +BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) + +TEST_SUITE_INITIALIZE(suite_init) +{ +} + +TEST_SUITE_CLEANUP(suite_cleanup) +{ +} + +TEST_FUNCTION_INITIALIZE(init) +{ +} + +TEST_FUNCTION_CLEANUP(cleanup) +{ + job_object_helper_deinit_for_test(); +} + +TEST_FUNCTION(job_object_helper_set_job_limits_with_zero_cpu_and_nonzero_memory_succeeds) +{ + /* This test verifies that calling job_object_helper_set_job_limits_to_current_process + with cpu=0 (skip CPU setup) and memory=1% + creates a job object with: + - CPU rate control not applied (ControlFlags == 0) + - Memory limit set to 1% of total physical memory */ + + ///arrange + char job_name[64]; + + ///act + THANDLE(JOB_OBJECT_HELPER) job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), 0, TEST_MEMORY_PERCENT); + + ///assert + /* Verify the job object was created */ + HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object"); + + /* Verify CPU rate control is not applied */ + JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_info = { 0 }; + DWORD return_length = 0; + BOOL query_result = QueryInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info"); + ASSERT_ARE_EQUAL(uint32_t, (uint32_t)0, cpu_info.ControlFlags, "CPU rate control should not be applied (ControlFlags == 0)"); + LogInfo("CPU rate control verified as not applied (ControlFlags=%" PRIu32 ")", cpu_info.ControlFlags); + + /* Verify memory limit is set */ + MEMORYSTATUSEX mem_status; + mem_status.dwLength = sizeof(mem_status); + ASSERT_IS_TRUE(GlobalMemoryStatusEx(&mem_status)); + SIZE_T expected_memory_in_MB = TEST_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_info = { 0 }; + query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info"); + ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Job memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)TEST_MEMORY_PERCENT); + ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.ProcessMemoryLimit / MEGABYTE, "Process memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)TEST_MEMORY_PERCENT); + LogInfo("Memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); + + /* Verify process is assigned */ + BOOL is_in_job = FALSE; + ASSERT_IS_TRUE(IsProcessInJob(GetCurrentProcess(), job_object, &is_in_job)); + ASSERT_IS_TRUE(is_in_job, "Current process should be assigned to the job object"); + + (void)CloseHandle(job_object); + + ///cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&job_object_helper, NULL); +} + +TEST_FUNCTION(job_object_helper_set_job_limits_with_nonzero_cpu_and_zero_memory_succeeds) +{ + /* This test verifies that calling job_object_helper_set_job_limits_to_current_process + with cpu=50% and memory=0 (skip memory setup) + creates a job object with: + - CPU rate control enabled with hard cap at 50% + - Memory limit not applied (LimitFlags == 0) */ + + ///arrange + char job_name[64]; + + ///act + THANDLE(JOB_OBJECT_HELPER) job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), TEST_CPU_PERCENT, 0); + + ///assert + /* Verify the job object was created */ + HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object"); + + /* Verify CPU rate control is enabled with correct rate */ + JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_info = { 0 }; + DWORD return_length = 0; + BOOL query_result = QueryInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info"); + ASSERT_ARE_EQUAL(uint32_t, (uint32_t)(JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP), cpu_info.ControlFlags, "CPU rate control should be enabled with hard cap"); + ASSERT_ARE_EQUAL(uint32_t, TEST_CPU_PERCENT * 100, cpu_info.CpuRate, "CPU rate should be %" PRIu32 "", TEST_CPU_PERCENT * 100); + LogInfo("CPU rate control verified: ControlFlags=%" PRIu32 ", CpuRate=%" PRIu32 "", cpu_info.ControlFlags, cpu_info.CpuRate); + + /* Verify memory limit is not applied (LimitFlags should not have memory flags) */ + JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_info = { 0 }; + query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info"); + ASSERT_ARE_EQUAL(uint32_t, (uint32_t)0, ext_info.BasicLimitInformation.LimitFlags & (JOB_OBJECT_LIMIT_JOB_MEMORY | JOB_OBJECT_LIMIT_PROCESS_MEMORY), "Memory limit flags should not be set"); + LogInfo("Memory limits verified as not applied (LimitFlags=0x%08lx)", (unsigned long)ext_info.BasicLimitInformation.LimitFlags); + + /* Verify process is assigned */ + BOOL is_in_job = FALSE; + ASSERT_IS_TRUE(IsProcessInJob(GetCurrentProcess(), job_object, &is_in_job)); + ASSERT_IS_TRUE(is_in_job, "Current process should be assigned to the job object"); + + (void)CloseHandle(job_object); + + ///cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&job_object_helper, NULL); +} + +TEST_FUNCTION(job_object_helper_reconfigure_both_limits_updated_succeeds) +{ + /* This test verifies that an existing job object created with both CPU and memory limits + can be reconfigured to update both limits: + (50% CPU, 1% memory) -> (30% CPU, 2% memory) */ + + ///arrange + char job_name[64]; + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); + + ///act - reconfigure to updated values + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, RECONFIGURED_CPU_PERCENT, RECONFIGURED_MEMORY_PERCENT); + + ///assert + ASSERT_IS_NOT_NULL(reconfigured_job_object_helper, "Reconfigure should succeed"); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper, "Reconfigured call should return same singleton"); + + HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after reconfigure"); + + /* Verify CPU rate control is updated */ + JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_info = { 0 }; + DWORD return_length = 0; + BOOL query_result = QueryInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after reconfigure"); + ASSERT_ARE_EQUAL(uint32_t, (uint32_t)(JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP), cpu_info.ControlFlags, "CPU rate control should be enabled with hard cap after reconfigure"); + ASSERT_ARE_EQUAL(uint32_t, RECONFIGURED_CPU_PERCENT * 100, cpu_info.CpuRate, "Reconfigured CPU rate should be %" PRIu32 "", RECONFIGURED_CPU_PERCENT * 100); + LogInfo("Reconfigured CPU rate verified: ControlFlags=%" PRIu32 ", CpuRate=%" PRIu32 "", cpu_info.ControlFlags, cpu_info.CpuRate); + + /* Verify memory limit is updated */ + MEMORYSTATUSEX mem_status; + mem_status.dwLength = sizeof(mem_status); + ASSERT_IS_TRUE(GlobalMemoryStatusEx(&mem_status)); + SIZE_T expected_memory_in_MB = RECONFIGURED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_info = { 0 }; + query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after reconfigure"); + ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Reconfigured job memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)RECONFIGURED_MEMORY_PERCENT); + ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.ProcessMemoryLimit / MEGABYTE, "Reconfigured process memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)RECONFIGURED_MEMORY_PERCENT); + LogInfo("Reconfigured memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); + + (void)CloseHandle(job_object); + + ///cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); +} + +TEST_FUNCTION(job_object_helper_reconfigure_memory_only_when_cpu_was_zero_succeeds) +{ + /* This test verifies that when the initial creation had cpu=0 (CPU not applied), + a reconfigure with cpu=0 (still not applied) and updated memory succeeds. + (0% CPU, 1% memory) -> (0% CPU, 2% memory) */ + + ///arrange + char job_name[64]; + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), 0, INITIAL_MEMORY_PERCENT); + + ///act - reconfigure: cpu still 0, memory updated + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 0, RECONFIGURED_MEMORY_PERCENT); + + ///assert + ASSERT_IS_NOT_NULL(reconfigured_job_object_helper, "Reconfigure should succeed"); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper, "Reconfigured call should return same singleton"); + + HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after reconfigure"); + + /* Verify CPU rate control is still not applied */ + JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_info = { 0 }; + DWORD return_length = 0; + BOOL query_result = QueryInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info"); + ASSERT_ARE_EQUAL(uint32_t, (uint32_t)0, cpu_info.ControlFlags, "CPU rate control should still not be applied"); + LogInfo("CPU rate control verified as still not applied (ControlFlags=%" PRIu32 ")", cpu_info.ControlFlags); + + /* Verify memory limit is updated */ + MEMORYSTATUSEX mem_status; + mem_status.dwLength = sizeof(mem_status); + ASSERT_IS_TRUE(GlobalMemoryStatusEx(&mem_status)); + SIZE_T expected_memory_in_MB = RECONFIGURED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_info = { 0 }; + query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after reconfigure"); + ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Reconfigured job memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)RECONFIGURED_MEMORY_PERCENT); + LogInfo("Reconfigured memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); + + (void)CloseHandle(job_object); + + ///cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); +} + +TEST_FUNCTION(job_object_helper_reconfigure_cpu_only_when_memory_was_zero_succeeds) +{ + /* This test verifies that when the initial creation had memory=0 (memory not applied), + a reconfigure with memory=0 (still not applied) and updated CPU succeeds. + (50% CPU, 0% memory) -> (30% CPU, 0% memory) */ + + ///arrange + char job_name[64]; + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, 0); + + ///act - reconfigure: cpu updated, memory still 0 + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, RECONFIGURED_CPU_PERCENT, 0); + + ///assert + ASSERT_IS_NOT_NULL(reconfigured_job_object_helper, "Reconfigure should succeed"); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper, "Reconfigured call should return same singleton"); + + HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after reconfigure"); + + /* Verify CPU rate control is updated */ + JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_info = { 0 }; + DWORD return_length = 0; + BOOL query_result = QueryInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after reconfigure"); + ASSERT_ARE_EQUAL(uint32_t, (uint32_t)(JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP), cpu_info.ControlFlags, "CPU rate control should be enabled with hard cap"); + ASSERT_ARE_EQUAL(uint32_t, RECONFIGURED_CPU_PERCENT * 100, cpu_info.CpuRate, "Reconfigured CPU rate should be %" PRIu32 "", RECONFIGURED_CPU_PERCENT * 100); + LogInfo("Reconfigured CPU rate verified: ControlFlags=%" PRIu32 ", CpuRate=%" PRIu32 "", cpu_info.ControlFlags, cpu_info.CpuRate); + + /* Verify memory limit is still not applied */ + JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_info = { 0 }; + query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after reconfigure"); + ASSERT_ARE_EQUAL(uint32_t, (uint32_t)0, ext_info.BasicLimitInformation.LimitFlags & (JOB_OBJECT_LIMIT_JOB_MEMORY | JOB_OBJECT_LIMIT_PROCESS_MEMORY), "Memory limit flags should still not be set"); + LogInfo("Memory limits verified as still not applied (LimitFlags=0x%08lx)", (unsigned long)ext_info.BasicLimitInformation.LimitFlags); + + (void)CloseHandle(job_object); + + ///cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); +} + +END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) diff --git a/win32/tests/job_object_helper_int/job_object_helper_reconfigure_int.c b/win32/tests/job_object_helper_int/job_object_helper_reconfigure_int.c index 5f476a41..edf8a315 100644 --- a/win32/tests/job_object_helper_int/job_object_helper_reconfigure_int.c +++ b/win32/tests/job_object_helper_int/job_object_helper_reconfigure_int.c @@ -30,6 +30,18 @@ struct JOB_OBJECT_HELPER_TAG { #define RECONFIGURED_CPU_PERCENT 30 #define RECONFIGURED_MEMORY_PERCENT 2 +static THANDLE(JOB_OBJECT_HELPER) create_job_object_with_limits(char* job_name_out, size_t job_name_size, uint32_t cpu, uint32_t memory) +{ + UUID_T job_name_uuid; + (void)uuid_produce(&job_name_uuid); + (void)snprintf(job_name_out, job_name_size, TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); + LogInfo("Creating job object (cpu=%" PRIu32 ", memory=%" PRIu32 ") with job name: %s...", cpu, memory, job_name_out); + + THANDLE(JOB_OBJECT_HELPER) result = job_object_helper_set_job_limits_to_current_process(job_name_out, cpu, memory); + ASSERT_IS_NOT_NULL(result, "Job object should be created (cpu=%" PRIu32 ", memory=%" PRIu32 ")", cpu, memory); + return result; +} + BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) TEST_SUITE_INITIALIZE(suite_init) @@ -57,16 +69,9 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_l * 3. The reconfigured limits are applied to the existing job object */ - UUID_T job_name_uuid; - (void)uuid_produce(&job_name_uuid); - - char job_name[64]; - (void)snprintf(job_name, sizeof(job_name), TEST_JOB_NAME_PREFIX "%" PRI_UUID_T "", UUID_T_VALUES(job_name_uuid)); - LogInfo("Running reconfigure test with job name: %s...", job_name); - // Step 1: Create the singleton with initial limits (50% CPU, 1% memory) - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); - ASSERT_IS_NOT_NULL(initial_job_object_helper); + char job_name[64]; + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); // Verify initial CPU rate HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); diff --git a/win32/tests/job_object_helper_ut/job_object_helper_ut.c b/win32/tests/job_object_helper_ut/job_object_helper_ut.c index 7f115699..3f075ff4 100644 --- a/win32/tests/job_object_helper_ut/job_object_helper_ut.c +++ b/win32/tests/job_object_helper_ut/job_object_helper_ut.c @@ -106,6 +106,26 @@ static void setup_job_object_helper_set_job_limits_to_current_process_expectatio setup_job_object_helper_set_job_limits_to_current_process_process_assign_expectations(); } +static THANDLE(JOB_OBJECT_HELPER) create_job_object_helper_singleton(uint32_t cpu, uint32_t memory) +{ + setup_job_object_helper_set_job_limits_to_current_process_createObjectA_expectations(); + if (cpu != 0) + { + setup_job_object_helper_limit_cpu_expectations(); + } + if (memory != 0) + { + setup_job_object_helper_limit_memory_expectations(); + } + setup_job_object_helper_set_job_limits_to_current_process_process_assign_expectations(); + + THANDLE(JOB_OBJECT_HELPER) result = job_object_helper_set_job_limits_to_current_process("job_name", cpu, memory); + ASSERT_IS_NOT_NULL(result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + umock_c_reset_all_calls(); + return result; +} + BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) @@ -146,11 +166,7 @@ TEST_FUNCTION_CLEANUP(cleanup) TEST_FUNCTION(job_object_helper_dispose_succeeds) { // arrange - setup_job_object_helper_set_job_limits_to_current_process_expectations(); - - THANDLE(JOB_OBJECT_HELPER) job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 1, 1); - ASSERT_IS_NOT_NULL(job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + THANDLE(JOB_OBJECT_HELPER) job_object_helper = create_job_object_helper_singleton(1, 1); // Release singleton ref (refcount 2->1), no dispose expected job_object_helper_deinit_for_test(); @@ -169,6 +185,8 @@ TEST_FUNCTION(job_object_helper_dispose_succeeds) /*Tests_SRS_JOB_OBJECT_HELPER_19_001: [ If percent_cpu is greater than 100 then job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_034: [ If percent_physical_memory is greater than 100 then job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_004: [ If percent_cpu is 0 or greater than 100, internal_job_object_helper_set_cpu_limit shall fail and return a non-zero value. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_010: [ If percent_physical_memory is 0 or greater than 100, internal_job_object_helper_set_memory_limit shall fail and return a non-zero value. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_test_limits) { // arrange @@ -190,12 +208,14 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_test_limits) /*Tests_SRS_JOB_OBJECT_HELPER_88_031: [ job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_create to create a new job object and assign it to the current process. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_035: [ internal_job_object_helper_create shall allocate a JOB_OBJECT_HELPER object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_036: [ internal_job_object_helper_create shall call CreateJobObjectA passing job_name for lpName and NULL for lpJobAttributes.]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_037: [ internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_005: [ If percent_cpu is not JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL, internal_job_object_helper_set_cpu_limit shall set ControlFlags to JOB_OBJECT_CPU_RATE_CONTROL_ENABLE and JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, and CpuRate to percent_cpu times 100. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_005: [ internal_job_object_helper_set_cpu_limit shall set ControlFlags to JOB_OBJECT_CPU_RATE_CONTROL_ENABLE and JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, and CpuRate to percent_cpu times 100. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_006: [ internal_job_object_helper_set_cpu_limit shall call SetInformationJobObject passing JobObjectCpuRateControlInformation and the JOBOBJECT_CPU_RATE_CONTROL_INFORMATION. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_038: [ internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_011: [ If percent_physical_memory is not JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, internal_job_object_helper_set_memory_limit shall call GlobalMemoryStatusEx to get the total amount of physical memory. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_012: [ If percent_physical_memory is not JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, internal_job_object_helper_set_memory_limit shall set JobMemoryLimit and ProcessMemoryLimit to percent_physical_memory percent of the physical memory and call SetInformationJobObject with JobObjectExtendedLimitInformation. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_042: [ On success, internal_job_object_helper_set_cpu_limit shall store percent_cpu in the singleton state. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_011: [ internal_job_object_helper_set_memory_limit shall call GlobalMemoryStatusEx to get the total amount of physical memory. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_012: [ internal_job_object_helper_set_memory_limit shall set JobMemoryLimit and ProcessMemoryLimit to percent_physical_memory percent of the physical memory and call SetInformationJobObject with JobObjectExtendedLimitInformation. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_043: [ On success, internal_job_object_helper_set_memory_limit shall store percent_physical_memory in the singleton state. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_039: [ internal_job_object_helper_create shall call GetCurrentProcess to get the current process handle. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_19_008: [ internal_job_object_helper_create shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_008: [ internal_job_object_helper_set_cpu_limit shall succeed and return 0. ]*/ @@ -206,35 +226,15 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_test_limits) TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_succeeds) { // arrange - int cpu_limits[3] = { 50, 20, JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL }; - int memory_limits[3] = { 50, JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, 20 }; + int cpu_limits[3] = { 50, 20, 0 }; + int memory_limits[3] = { 50, 0, 20 }; ASSERT_ARE_EQUAL(int, MU_COUNT_ARRAY_ITEMS(cpu_limits), MU_COUNT_ARRAY_ITEMS(memory_limits), "cpu_limits and memory_limits must have the same number of items"); for (int i = 0; i < MU_COUNT_ARRAY_ITEMS(cpu_limits); ++i) { - umock_c_reset_all_calls(); - setup_job_object_helper_set_job_limits_to_current_process_createObjectA_expectations(); - setup_job_object_helper_limit_cpu_expectations(); - if (memory_limits[i] == JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT) - { - // DISABLE path: no GlobalMemoryStatusEx, just SetInformationJobObject with LimitFlags=0 - STRICT_EXPECTED_CALL(mocked_SetInformationJobObject(IGNORED_ARG, JobObjectExtendedLimitInformation, IGNORED_ARG, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))) - .SetReturn(TRUE) - .SetFailReturn(FALSE); - } - else - { - setup_job_object_helper_limit_memory_expectations(); - } - setup_job_object_helper_set_job_limits_to_current_process_process_assign_expectations(); - // act - THANDLE(JOB_OBJECT_HELPER) result = job_object_helper_set_job_limits_to_current_process("job_name", cpu_limits[i], memory_limits[i]); - - // assert - ASSERT_IS_NOT_NULL(result); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + THANDLE(JOB_OBJECT_HELPER) result = create_job_object_helper_singleton(cpu_limits[i], memory_limits[i]); // cleanup - release result, then deinit releases singleton which triggers dispose THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&result, NULL); @@ -248,13 +248,13 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_succeeds) } } -/*Tests_SRS_JOB_OBJECT_HELPER_88_022: [ If job_object_singleton_state.job_object_helper is NULL and percent_cpu is JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL and percent_physical_memory is JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, job_object_helper_set_job_limits_to_current_process shall return NULL without creating a job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_040: [ If percent_cpu is 0 and percent_physical_memory is 0 then job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_023: [ If job_object_singleton_state.job_object_helper is NULL and both percent_cpu and percent_physical_memory are 100, job_object_helper_set_job_limits_to_current_process shall return NULL without creating a job object. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_no_effective_limits) { // arrange - int cpu_limits[2] = { JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL, 100 }; - int memory_limits[2] = { JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, 100 }; + int cpu_limits[2] = { 0, 100 }; + int memory_limits[2] = { 0, 100 }; for (int i = 0; i < MU_COUNT_ARRAY_ITEMS(cpu_limits); ++i) { @@ -271,19 +271,14 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_w /*Tests_SRS_JOB_OBJECT_HELPER_88_030: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall not create a new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_002: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_reconfigure to apply the limits to the existing job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If percent_cpu is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If percent_physical_memory is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_with_same_params) { // arrange - create singleton - setup_job_object_helper_set_job_limits_to_current_process_expectations(); - - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(initial_job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - umock_c_reset_all_calls(); + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); // expect reconfigure calls (idempotent: same values applied again) setup_job_object_helper_limit_cpu_expectations(); @@ -302,93 +297,121 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_w THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reused_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_004: [ If percent_cpu is JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL, internal_job_object_helper_set_cpu_limit shall call SetInformationJobObject passing JobObjectCpuRateControlInformation with ControlFlags set to 0 to disable CPU rate control. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_removes_cpu_limit_when_nonzero_cpu_becomes_zero) +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_reconfigure_cpu_from_nonzero_to_zero) { // arrange - create singleton with (50, 50) - setup_job_object_helper_set_job_limits_to_current_process_expectations(); + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); + + // act - reconfigure cpu from 50->0 should fail (SRS_88_041) + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 0, 50); - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(initial_job_object_helper); + // assert - should fail because percent_cpu was non-zero (50) and now being set to 0 + ASSERT_IS_NULL(reconfigured_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - umock_c_reset_all_calls(); - // expect CPU limit removal + memory limit (idempotent) - setup_job_object_helper_limit_cpu_expectations(); - setup_job_object_helper_limit_memory_expectations(); + // cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); +} - // act - remove cpu limit (50->JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL) - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL, 50); +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_reconfigure_memory_from_nonzero_to_zero) +{ + // arrange - create singleton with (50, 50) + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); - // assert - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + // act - reconfigure memory from 50->0 should fail (SRS_88_041) + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 0); - // verify ControlFlags == 0 (rate control disabled) - ASSERT_ARE_EQUAL(uint32_t, 0, captured_cpu_rate_control_information.ControlFlags); + // assert - should fail because percent_memory was non-zero (50) and now being set to 0 + ASSERT_IS_NULL(reconfigured_job_object_helper); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); // cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_010: [ If percent_physical_memory is JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, internal_job_object_helper_set_memory_limit shall call SetInformationJobObject passing JobObjectExtendedLimitInformation with LimitFlags set to 0 to remove memory limits. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_removes_memory_limit_when_nonzero_memory_becomes_zero) +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(when_created_with_cpu_0_memory_50_then_reconfigure_cpu_50_memory_0_fails) { - // arrange - create singleton with (50, 50) - setup_job_object_helper_set_job_limits_to_current_process_expectations(); + // arrange - create singleton with cpu=0, memory=50 + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(0, 50); - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(initial_job_object_helper); + // act - reconfigure to cpu=50, memory=0. memory was 50 (non-zero) so setting to 0 should fail + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 0); + + // assert - should fail because percent_memory was non-zero (50) and now being set to 0 + ASSERT_IS_NULL(reconfigured_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - umock_c_reset_all_calls(); - // expect CPU limit (idempotent) + memory limit removal (SetInformationJobObject with LimitFlags=0) - setup_job_object_helper_limit_cpu_expectations(); - STRICT_EXPECTED_CALL(mocked_SetInformationJobObject(IGNORED_ARG, JobObjectExtendedLimitInformation, IGNORED_ARG, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))) - .SetReturn(TRUE) - .SetFailReturn(FALSE); + // cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); +} - // act - remove memory limit (50->JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT) - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT); +/*Tests_SRS_JOB_OBJECT_HELPER_88_042: [ On success, internal_job_object_helper_set_cpu_limit shall store percent_cpu in the singleton state. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(when_reconfigure_sets_cpu_then_state_is_updated_and_subsequent_zero_cpu_fails) +{ + // arrange - create singleton with cpu=0, memory=50 (only memory limit) + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(0, 50); - // assert + // Reconfigure to (30, 50) — this should store percent_cpu=30 in singleton state + setup_job_object_helper_limit_cpu_expectations(); + setup_job_object_helper_limit_memory_expectations(); + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 50); ASSERT_IS_NOT_NULL(reconfigured_job_object_helper); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - // verify LimitFlags == 0 (memory limits removed) - ASSERT_ARE_EQUAL(uint32_t, 0, (uint32_t)captured_extended_limit_information.BasicLimitInformation.LimitFlags); + // act - try to reconfigure cpu back to 0. If state was updated, percent_cpu is now 30 + // and this should fail (SRS_88_041). If state was NOT updated, percent_cpu would still + // be 0 and this would incorrectly succeed. + THANDLE(JOB_OBJECT_HELPER) zero_cpu_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 0, 50); + + // assert - must fail because percent_cpu was stored as 30 during reconfiguration + ASSERT_IS_NULL(zero_cpu_job_object_helper); // cleanup + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); +} + +/*Tests_SRS_JOB_OBJECT_HELPER_88_043: [ On success, internal_job_object_helper_set_memory_limit shall store percent_physical_memory in the singleton state. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(when_reconfigure_sets_memory_then_state_is_updated_and_subsequent_zero_memory_fails) +{ + // arrange - create singleton with cpu=50, memory=0 (only CPU limit) + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 0); + + // Reconfigure to (50, 30) — this should store percent_memory=30 in singleton state + setup_job_object_helper_limit_cpu_expectations(); + setup_job_object_helper_limit_memory_expectations(); + THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 30); + ASSERT_IS_NOT_NULL(reconfigured_job_object_helper); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + // act - try to reconfigure memory back to 0. If state was updated, percent_memory is + // now 30 and this should fail (SRS_88_041). If state was NOT updated, percent_memory + // would still be 0 and this would incorrectly succeed. + THANDLE(JOB_OBJECT_HELPER) zero_memory_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 0); + + // assert - must fail because percent_memory was stored as 30 during reconfiguration + ASSERT_IS_NULL(zero_memory_job_object_helper); + + // cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); } /*Tests_SRS_JOB_OBJECT_HELPER_88_030: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall not create a new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_002: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_reconfigure to apply the limits to the existing job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If percent_cpu is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If percent_physical_memory is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_when_params_change) { // arrange - create singleton with (50, 50) - setup_job_object_helper_set_job_limits_to_current_process_expectations(); - - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(initial_job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - umock_c_reset_all_calls(); + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); // expect reconfigure calls: CPU limit change + memory limit change setup_job_object_helper_limit_cpu_expectations(); @@ -414,19 +437,14 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_w THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If percent_cpu is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If percent_physical_memory is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_to_100_100) { // arrange - create singleton with (50, 50) - setup_job_object_helper_set_job_limits_to_current_process_expectations(); - - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(initial_job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - umock_c_reset_all_calls(); + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); // expect reconfigure calls: CPU limit change + memory limit change setup_job_object_helper_limit_cpu_expectations(); @@ -459,12 +477,7 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_t TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_cpu_reconfigure_fails) { // arrange - create singleton with (50, 50) - setup_job_object_helper_set_job_limits_to_current_process_expectations(); - - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(initial_job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - umock_c_reset_all_calls(); + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); // expect CPU SetInformationJobObject to fail STRICT_EXPECTED_CALL(mocked_SetInformationJobObject(IGNORED_ARG, JobObjectCpuRateControlInformation, IGNORED_ARG, sizeof(JOBOBJECT_CPU_RATE_CONTROL_INFORMATION))) @@ -487,12 +500,7 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_w TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_memory_reconfigure_fails) { // arrange - create singleton with (50, 50) - setup_job_object_helper_set_job_limits_to_current_process_expectations(); - - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(initial_job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - umock_c_reset_all_calls(); + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); // expect CPU reconfigure succeeds, but memory GlobalMemoryStatusEx fails setup_job_object_helper_limit_cpu_expectations(); @@ -522,23 +530,13 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_w } /*Tests_SRS_JOB_OBJECT_HELPER_88_027: [ job_object_helper_deinit_for_test shall release the singleton THANDLE(JOB_OBJECT_HELPER) by assigning it to NULL. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_044: [ job_object_helper_deinit_for_test shall reset percent_cpu to 0 in the singleton state. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_045: [ job_object_helper_deinit_for_test shall reset percent_memory to 0 in the singleton state. ]*/ TEST_FUNCTION(job_object_helper_deinit_for_test_resets_singleton) { // arrange - create singleton with (50, 50) - setup_job_object_helper_set_job_limits_to_current_process_createObjectA_expectations(); - setup_job_object_helper_limit_cpu_expectations(); - setup_job_object_helper_limit_memory_expectations(); - STRICT_EXPECTED_CALL(mocked_GetCurrentProcess()) - .CallCannotFail(); - STRICT_EXPECTED_CALL(mocked_AssignProcessToJobObject(IGNORED_ARG, IGNORED_ARG)) - .SetReturn(TRUE) - .SetFailReturn(FALSE); - - THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(initial_job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - umock_c_reset_all_calls(); STRICT_EXPECTED_CALL(mocked_CloseHandle(IGNORED_ARG)); STRICT_EXPECTED_CALL(free(IGNORED_ARG)); @@ -550,19 +548,7 @@ TEST_FUNCTION(job_object_helper_deinit_for_test_resets_singleton) ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); // assert - verify a new singleton can be created (proving deinit reset the state) - umock_c_reset_all_calls(); - setup_job_object_helper_set_job_limits_to_current_process_createObjectA_expectations(); - setup_job_object_helper_limit_cpu_expectations(); - setup_job_object_helper_limit_memory_expectations(); - STRICT_EXPECTED_CALL(mocked_GetCurrentProcess()) - .CallCannotFail(); - STRICT_EXPECTED_CALL(mocked_AssignProcessToJobObject(IGNORED_ARG, IGNORED_ARG)) - .SetReturn(TRUE) - .SetFailReturn(FALSE); - - THANDLE(JOB_OBJECT_HELPER) new_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 50); - ASSERT_IS_NOT_NULL(new_job_object_helper); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + THANDLE(JOB_OBJECT_HELPER) new_job_object_helper = create_job_object_helper_singleton(50, 50); // cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&new_job_object_helper, NULL); @@ -571,13 +557,13 @@ TEST_FUNCTION(job_object_helper_deinit_for_test_resets_singleton) /*Tests_SRS_JOB_OBJECT_HELPER_88_031: [ job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_create to create a new job object and assign it to the current process. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_035: [ internal_job_object_helper_create shall allocate a JOB_OBJECT_HELPER object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_036: [ internal_job_object_helper_create shall call CreateJobObjectA passing job_name for lpName and NULL for lpJobAttributes.]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_037: [ internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_005: [ If percent_cpu is not JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL, internal_job_object_helper_set_cpu_limit shall set ControlFlags to JOB_OBJECT_CPU_RATE_CONTROL_ENABLE and JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, and CpuRate to percent_cpu times 100. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_005: [ internal_job_object_helper_set_cpu_limit shall set ControlFlags to JOB_OBJECT_CPU_RATE_CONTROL_ENABLE and JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, and CpuRate to percent_cpu times 100. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_006: [ internal_job_object_helper_set_cpu_limit shall call SetInformationJobObject passing JobObjectCpuRateControlInformation and the JOBOBJECT_CPU_RATE_CONTROL_INFORMATION. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_007: [ If SetInformationJobObject fails, internal_job_object_helper_set_cpu_limit shall fail and return a non-zero value. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_038: [ internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_011: [ If percent_physical_memory is not JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, internal_job_object_helper_set_memory_limit shall call GlobalMemoryStatusEx to get the total amount of physical memory. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_012: [ If percent_physical_memory is not JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT, internal_job_object_helper_set_memory_limit shall set JobMemoryLimit and ProcessMemoryLimit to percent_physical_memory percent of the physical memory and call SetInformationJobObject with JobObjectExtendedLimitInformation. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_011: [ internal_job_object_helper_set_memory_limit shall call GlobalMemoryStatusEx to get the total amount of physical memory. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_012: [ internal_job_object_helper_set_memory_limit shall set JobMemoryLimit and ProcessMemoryLimit to percent_physical_memory percent of the physical memory and call SetInformationJobObject with JobObjectExtendedLimitInformation. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_013: [ If there are any failures, internal_job_object_helper_set_memory_limit shall fail and return a non-zero value. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_039: [ internal_job_object_helper_create shall call GetCurrentProcess to get the current process handle. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_19_008: [ internal_job_object_helper_create shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ From e1713508966a59bd01ae8e937a2048cfe54752cf Mon Sep 17 00:00:00 2001 From: Shrikant Jadhav Date: Wed, 11 Mar 2026 15:39:15 +0530 Subject: [PATCH 2/4] removed unwanted define from header --- win32/inc/c_pal/job_object_helper.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/win32/inc/c_pal/job_object_helper.h b/win32/inc/c_pal/job_object_helper.h index e78be620..7f270aab 100644 --- a/win32/inc/c_pal/job_object_helper.h +++ b/win32/inc/c_pal/job_object_helper.h @@ -15,12 +15,6 @@ #include "c_pal/thandle.h" -// Passing JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL as percent_cpu disables CPU rate control (removes the throttle) -#define JOB_OBJECT_HELPER_DISABLE_CPU_RATE_CONTROL 0 - -// Passing JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT as percent_physical_memory removes memory limits -#define JOB_OBJECT_HELPER_DISABLE_MEMORY_LIMIT 0 - typedef struct JOB_OBJECT_HELPER_TAG JOB_OBJECT_HELPER; THANDLE_TYPE_DECLARE(JOB_OBJECT_HELPER); From a11fc454735c833da542ac768170039830389b74 Mon Sep 17 00:00:00 2001 From: Shrikant Jadhav Date: Wed, 11 Mar 2026 17:06:47 +0530 Subject: [PATCH 3/4] fixed repo validation --- win32/devdoc/job_object_helper_requirements.md | 4 +++- win32/src/job_object_helper.c | 5 +++-- .../job_object_helper_ut/job_object_helper_ut.c | 12 ++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/win32/devdoc/job_object_helper_requirements.md b/win32/devdoc/job_object_helper_requirements.md index 053cf892..f9f1b68d 100644 --- a/win32/devdoc/job_object_helper_requirements.md +++ b/win32/devdoc/job_object_helper_requirements.md @@ -121,7 +121,9 @@ The function implements a process-level singleton pattern to prevent Job Object **SRS_JOB_OBJECT_HELPER_88_030: [** If `job_object_singleton_state.job_object_helper` is not `NULL`, `job_object_helper_set_job_limits_to_current_process` shall not create a new job object. **]** -**SRS_JOB_OBJECT_HELPER_88_041: [** During reconfiguration, if `percent_cpu` is `0` and the `job_object_singleton_state.percent_cpu` is non-zero, or if `percent_physical_memory` is `0` and the `job_object_singleton_state.percent_memory` is non-zero, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** +**SRS_JOB_OBJECT_HELPER_88_041: [** During reconfiguration, if `percent_cpu` is `0` and the `job_object_singleton_state.percent_cpu` is non-zero, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** + +**SRS_JOB_OBJECT_HELPER_88_046: [** During reconfiguration, if `percent_physical_memory` is `0` and the `job_object_singleton_state.percent_memory` is non-zero, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** **SRS_JOB_OBJECT_HELPER_88_002: [** If `job_object_singleton_state.job_object_helper` is not `NULL`, `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_reconfigure` to apply the limits to the existing job object. **]** diff --git a/win32/src/job_object_helper.c b/win32/src/job_object_helper.c index b0010067..0b864f69 100644 --- a/win32/src/job_object_helper.c +++ b/win32/src/job_object_helper.c @@ -332,9 +332,10 @@ IMPLEMENT_MOCKABLE_FUNCTION(, THANDLE(JOB_OBJECT_HELPER), job_object_helper_set_ LogWarning("Reconfiguring existing process-level singleton Job Object (cpu: %" PRIu32 ", memory: %" PRIu32 ")", percent_cpu, percent_physical_memory); - if(/*Codes_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + if( + /*Codes_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ (percent_cpu == 0 && job_object_singleton_state.percent_cpu != 0) || - /*Codes_SRS_JOB_OBJECT_HELPER_88_042: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_046: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ (percent_physical_memory == 0 && job_object_singleton_state.percent_memory != 0)) { LogError("Invalid arguments: percent_cpu or percent_physical_memory cannot be set to 0 once it has been set to a non-zero value. Received percent_cpu=%" PRIu32 ", received percent_physical_memory=%" PRIu32 ", current percent_cpu=%" PRIu32 ", current percent_physical_memory=%" PRIu32 "", percent_cpu, percent_physical_memory, job_object_singleton_state.percent_cpu, job_object_singleton_state.percent_memory); diff --git a/win32/tests/job_object_helper_ut/job_object_helper_ut.c b/win32/tests/job_object_helper_ut/job_object_helper_ut.c index 3f075ff4..863c9f04 100644 --- a/win32/tests/job_object_helper_ut/job_object_helper_ut.c +++ b/win32/tests/job_object_helper_ut/job_object_helper_ut.c @@ -236,7 +236,7 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_succeeds) // act THANDLE(JOB_OBJECT_HELPER) result = create_job_object_helper_singleton(cpu_limits[i], memory_limits[i]); - // cleanup - release result, then deinit releases singleton which triggers dispose + // assert THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&result, NULL); // Expect dispose (CloseHandle on job object) + free of THANDLE wrapper when singleton refcount reaches 0 @@ -297,7 +297,7 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_w THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reused_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_reconfigure_cpu_from_nonzero_to_zero) { // arrange - create singleton with (50, 50) @@ -314,7 +314,7 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_w THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_reconfigure_memory_from_nonzero_to_zero) { // arrange - create singleton with (50, 50) @@ -331,7 +331,7 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_w THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ TEST_FUNCTION(when_created_with_cpu_0_memory_50_then_reconfigure_cpu_50_memory_0_fails) { // arrange - create singleton with cpu=0, memory=50 @@ -349,7 +349,7 @@ TEST_FUNCTION(when_created_with_cpu_0_memory_50_then_reconfigure_cpu_50_memory_0 } /*Tests_SRS_JOB_OBJECT_HELPER_88_042: [ On success, internal_job_object_helper_set_cpu_limit shall store percent_cpu in the singleton state. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ TEST_FUNCTION(when_reconfigure_sets_cpu_then_state_is_updated_and_subsequent_zero_cpu_fails) { // arrange - create singleton with cpu=0, memory=50 (only memory limit) @@ -376,7 +376,7 @@ TEST_FUNCTION(when_reconfigure_sets_cpu_then_state_is_updated_and_subsequent_zer } /*Tests_SRS_JOB_OBJECT_HELPER_88_043: [ On success, internal_job_object_helper_set_memory_limit shall store percent_physical_memory in the singleton state. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, or if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ TEST_FUNCTION(when_reconfigure_sets_memory_then_state_is_updated_and_subsequent_zero_memory_fails) { // arrange - create singleton with cpu=50, memory=0 (only CPU limit) From 892db509ec7b1b84baa02874d10dd521b93596bf Mon Sep 17 00:00:00 2001 From: Shrikant Jadhav Date: Sun, 15 Mar 2026 21:52:30 +0530 Subject: [PATCH 4/4] addressed review comments --- .../devdoc/job_object_helper_requirements.md | 68 +++---- win32/src/job_object_helper.c | 55 +++--- .../job_object_helper_int/CMakeLists.txt | 2 +- .../job_object_helper_negative_int.c | 16 +- .../job_object_helper_partial_limits_int.c | 86 ++++----- ...e_int.c => job_object_helper_update_int.c} | 80 ++++---- .../job_object_helper_ut.c | 173 ++++++++---------- 7 files changed, 223 insertions(+), 257 deletions(-) rename win32/tests/job_object_helper_int/{job_object_helper_reconfigure_int.c => job_object_helper_update_int.c} (65%) diff --git a/win32/devdoc/job_object_helper_requirements.md b/win32/devdoc/job_object_helper_requirements.md index f9f1b68d..3bf6bfb2 100644 --- a/win32/devdoc/job_object_helper_requirements.md +++ b/win32/devdoc/job_object_helper_requirements.md @@ -2,7 +2,7 @@ `job_object_helper` is a module that wraps the Windows JobObject API, primarily for the purpose of limiting the amount of memory and CPU the current process can consume. -Note: Windows Job Objects have a critical property: `CloseHandle` on a job object handle does NOT disassociate the process from the job object. If multiple job objects are created and the process is assigned to each, the CPU rate limits compound multiplicatively (e.g., two 50% caps result in 25% effective CPU). Since this module calls `AssignProcessToJobObject` on the current process, it uses a process-level singleton pattern internally — the job object is created once and reused on subsequent calls. If subsequent calls have different parameters, the existing job object limits are reconfigured in-place via `SetInformationJobObject`. During reconfiguration, CPU and memory limits are updated independently. If one succeeds but the other fails, the function returns failure even though the successful change has already been applied to the Windows job object. The caller can simply retry to re-apply the limits. +Note: Windows Job Objects have a critical property: `CloseHandle` on a job object handle does NOT disassociate the process from the job object. If multiple job objects are created and the process is assigned to each, the CPU rate limits compound multiplicatively (e.g., two 50% caps result in 25% effective CPU). Since this module calls `AssignProcessToJobObject` on the current process, it uses a process-level singleton pattern internally — the job object is created once and reused on subsequent calls. If subsequent calls have different parameters, the existing job object limits are updated in-place via `SetInformationJobObject`. CPU and memory limits are updated independently. If one succeeds but the other fails, the function returns failure even though the successful change has already been applied to the Windows job object. The caller can simply retry to re-apply the limits. Note: `job_object_helper_set_job_limits_to_current_process` and `job_object_helper_deinit_for_test` are NOT thread-safe. These functions must be called from a single thread. Concurrent calls from multiple threads may result in undefined behavior due to non-atomic singleton initialization and cleanup. @@ -65,53 +65,13 @@ static int internal_job_object_helper_set_memory_limit(HANDLE job_object, uint32 **SRS_JOB_OBJECT_HELPER_88_014: [** `internal_job_object_helper_set_memory_limit` shall succeed and return `0`. **]** -## internal_job_object_helper_reconfigure -```c -static int internal_job_object_helper_reconfigure(uint32_t percent_cpu, uint32_t percent_physical_memory); -``` -`internal_job_object_helper_reconfigure` is an internal function that reconfigures the existing process-level singleton job object with new CPU and memory limits. - -**SRS_JOB_OBJECT_HELPER_88_003: [** If `percent_cpu` is not `0`, `internal_job_object_helper_reconfigure` shall call `internal_job_object_helper_set_cpu_limit` to apply the CPU rate control to the Windows job object. **]** - -**SRS_JOB_OBJECT_HELPER_88_009: [** If `percent_physical_memory` is not `0`, `internal_job_object_helper_reconfigure` shall call `internal_job_object_helper_set_memory_limit` to apply the memory limit to the Windows job object. **]** - -**SRS_JOB_OBJECT_HELPER_88_017: [** If there are any failures, `internal_job_object_helper_reconfigure` shall fail and return a non-zero value. **]** - -**SRS_JOB_OBJECT_HELPER_88_020: [** On successful reconfiguration, `internal_job_object_helper_reconfigure` shall return `0`. **]** - - -## internal_job_object_helper_create -```c -static int internal_job_object_helper_create(const char* job_name, uint32_t percent_cpu, uint32_t percent_physical_memory); -``` -`internal_job_object_helper_create` is an internal function that creates a new job object, applies CPU and memory limits, assigns it to the current process, and stores it in the process-level singleton state. - -**SRS_JOB_OBJECT_HELPER_88_035: [** `internal_job_object_helper_create` shall allocate a `JOB_OBJECT_HELPER` object. **]** - -**SRS_JOB_OBJECT_HELPER_88_036: [** `internal_job_object_helper_create` shall call `CreateJobObjectA` passing `job_name` for `lpName` and `NULL` for `lpJobAttributes`. **]** - -**SRS_JOB_OBJECT_HELPER_88_037: [** If `percent_cpu` is not `0`, `internal_job_object_helper_create` shall call `internal_job_object_helper_set_cpu_limit` to apply the CPU rate control to the Windows job object. **]** - -**SRS_JOB_OBJECT_HELPER_88_038: [** If `percent_physical_memory` is not `0`, `internal_job_object_helper_create` shall call `internal_job_object_helper_set_memory_limit` to apply the memory limit to the Windows job object. **]** - -**SRS_JOB_OBJECT_HELPER_88_039: [** `internal_job_object_helper_create` shall call `GetCurrentProcess` to get the current process handle. **]** - -**SRS_JOB_OBJECT_HELPER_19_008: [** `internal_job_object_helper_create` shall call `AssignProcessToJobObject` to assign the current process to the new job object. **]** - -**SRS_JOB_OBJECT_HELPER_88_024: [** On success, `internal_job_object_helper_create` shall store the `THANDLE(JOB_OBJECT_HELPER)` in the process-level singleton state. **]** - -**SRS_JOB_OBJECT_HELPER_88_032: [** If there are any failures, `internal_job_object_helper_create` shall fail and return a non-zero value. **]** - -**SRS_JOB_OBJECT_HELPER_88_033: [** `internal_job_object_helper_create` shall succeed and return `0`. **]** - - ## job_object_helper_set_job_limits_to_current_process ```c MOCKABLE_FUNCTION(, THANDLE(JOB_OBJECT_HELPER), job_object_helper_set_job_limits_to_current_process, const char*, job_name, uint32_t, percent_cpu, uint32_t, percent_physical_memory); ``` `job_object_helper_set_job_limits_to_current_process` creates the Job Object with limits if not present and assigns the current process to it. Returns a THANDLE to allow reuse of the job object across multiple processes. If being used only in single process, then handle can be released immediately and process continues having the set limits. -The function implements a process-level singleton pattern to prevent Job Object accumulation. On the first call, it creates the job object. On subsequent calls, the existing job object's limits are applied in-place via `SetInformationJobObject` and the existing singleton is returned. Passing `0` for both `percent_cpu` and `percent_physical_memory` is not allowed and will return `NULL`. During reconfiguration, a value of `0` is not allowed for a parameter that was previously set to a non-zero value. +The function implements a process-level singleton pattern to prevent Job Object accumulation. On the first call, it creates the job object. On subsequent calls, the existing job object's limits are updated in-place via `SetInformationJobObject` and the existing singleton is returned. Passing `0` for both `percent_cpu` and `percent_physical_memory` is not allowed and will return `NULL`. When the singleton already exists, a value of `0` is not allowed for a parameter that was previously set to a non-zero value. **SRS_JOB_OBJECT_HELPER_19_001: [** If `percent_cpu` is greater than `100` then `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** @@ -121,17 +81,31 @@ The function implements a process-level singleton pattern to prevent Job Object **SRS_JOB_OBJECT_HELPER_88_030: [** If `job_object_singleton_state.job_object_helper` is not `NULL`, `job_object_helper_set_job_limits_to_current_process` shall not create a new job object. **]** -**SRS_JOB_OBJECT_HELPER_88_041: [** During reconfiguration, if `percent_cpu` is `0` and the `job_object_singleton_state.percent_cpu` is non-zero, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** +**SRS_JOB_OBJECT_HELPER_88_041: [** If `job_object_singleton_state.job_object_helper` is not `NULL` and `percent_cpu` is `0` and the `job_object_singleton_state.percent_cpu` is non-zero, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** -**SRS_JOB_OBJECT_HELPER_88_046: [** During reconfiguration, if `percent_physical_memory` is `0` and the `job_object_singleton_state.percent_memory` is non-zero, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** +**SRS_JOB_OBJECT_HELPER_88_046: [** If `job_object_singleton_state.job_object_helper` is not `NULL` and `percent_physical_memory` is `0` and the `job_object_singleton_state.percent_memory` is non-zero, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** -**SRS_JOB_OBJECT_HELPER_88_002: [** If `job_object_singleton_state.job_object_helper` is not `NULL`, `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_reconfigure` to apply the limits to the existing job object. **]** +**SRS_JOB_OBJECT_HELPER_88_003: [** If `job_object_singleton_state.job_object_helper` is not `NULL` and `percent_cpu` is not `0`, `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_set_cpu_limit` to apply the CPU rate control to the existing job object. **]** -**SRS_JOB_OBJECT_HELPER_88_021: [** If `internal_job_object_helper_reconfigure` returns `0`, `job_object_helper_set_job_limits_to_current_process` shall increment the reference count on the existing `THANDLE(JOB_OBJECT_HELPER)` and return it. **]** +**SRS_JOB_OBJECT_HELPER_88_009: [** If `job_object_singleton_state.job_object_helper` is not `NULL` and `percent_physical_memory` is not `0`, `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_set_memory_limit` to apply the memory limit to the existing job object. **]** + +**SRS_JOB_OBJECT_HELPER_88_021: [** On successful update of the existing job object, `job_object_helper_set_job_limits_to_current_process` shall increment the reference count on the existing `THANDLE(JOB_OBJECT_HELPER)` and return it. **]** **SRS_JOB_OBJECT_HELPER_88_023: [** If `job_object_singleton_state.job_object_helper` is `NULL` and both `percent_cpu` and `percent_physical_memory` are `100`, `job_object_helper_set_job_limits_to_current_process` shall return `NULL` without creating a job object. **]** -**SRS_JOB_OBJECT_HELPER_88_031: [** `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_create` to create a new job object and assign it to the current process. **]** +**SRS_JOB_OBJECT_HELPER_88_035: [** `job_object_helper_set_job_limits_to_current_process` shall allocate a `JOB_OBJECT_HELPER` object. **]** + +**SRS_JOB_OBJECT_HELPER_88_036: [** `job_object_helper_set_job_limits_to_current_process` shall call `CreateJobObjectA` passing `job_name` for `lpName` and `NULL` for `lpJobAttributes`. **]** + +**SRS_JOB_OBJECT_HELPER_88_037: [** If `percent_cpu` is not `0`, `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_set_cpu_limit` to apply the CPU rate control to the new job object. **]** + +**SRS_JOB_OBJECT_HELPER_88_038: [** If `percent_physical_memory` is not `0`, `job_object_helper_set_job_limits_to_current_process` shall call `internal_job_object_helper_set_memory_limit` to apply the memory limit to the new job object. **]** + +**SRS_JOB_OBJECT_HELPER_88_039: [** `job_object_helper_set_job_limits_to_current_process` shall call `GetCurrentProcess` to get the current process handle. **]** + +**SRS_JOB_OBJECT_HELPER_19_008: [** `job_object_helper_set_job_limits_to_current_process` shall call `AssignProcessToJobObject` to assign the current process to the new job object. **]** + +**SRS_JOB_OBJECT_HELPER_88_024: [** On success, `job_object_helper_set_job_limits_to_current_process` shall store the `THANDLE(JOB_OBJECT_HELPER)` in the process-level singleton state. **]** **SRS_JOB_OBJECT_HELPER_19_009: [** If there are any failures, `job_object_helper_set_job_limits_to_current_process` shall fail and return `NULL`. **]** diff --git a/win32/src/job_object_helper.c b/win32/src/job_object_helper.c index 0b864f69..2154d47d 100644 --- a/win32/src/job_object_helper.c +++ b/win32/src/job_object_helper.c @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#include #include #include @@ -143,17 +144,17 @@ static int internal_job_object_helper_set_memory_limit(HANDLE job_object, uint32 /* Note: CPU and memory limits are always applied unconditionally (even if the values haven't changed) to keep the code simple. The caller can simply retry on failure. */ -static int internal_job_object_helper_reconfigure(uint32_t percent_cpu, uint32_t percent_physical_memory) +static int internal_job_object_helper_update(uint32_t percent_cpu, uint32_t percent_physical_memory) { int result; bool failed = false; - /*Codes_SRS_JOB_OBJECT_HELPER_88_003: [ If percent_cpu is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_003: [ If job_object_singleton_state.job_object_helper is not NULL and percent_cpu is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the existing job object. ]*/ if(percent_cpu != 0) { if (internal_job_object_helper_set_cpu_limit(job_object_singleton_state.job_object_helper->job_object, percent_cpu) != 0) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ LogError("failure in internal_job_object_helper_set_cpu_limit(job_object=%p, percent_cpu=%" PRIu32 ") during reconfiguration", job_object_singleton_state.job_object_helper->job_object, percent_cpu); failed = true; @@ -174,12 +175,12 @@ static int internal_job_object_helper_reconfigure(uint32_t percent_cpu, uint32_t } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_009: [ If percent_physical_memory is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_009: [ If job_object_singleton_state.job_object_helper is not NULL and percent_physical_memory is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the existing job object. ]*/ if(percent_physical_memory != 0) { if (internal_job_object_helper_set_memory_limit(job_object_singleton_state.job_object_helper->job_object, percent_physical_memory) != 0) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ LogError("failure in internal_job_object_helper_set_memory_limit(job_object=%p, percent_physical_memory=%" PRIu32 ") during reconfiguration", job_object_singleton_state.job_object_helper->job_object, percent_physical_memory); failed = true; @@ -197,12 +198,12 @@ static int internal_job_object_helper_reconfigure(uint32_t percent_cpu, uint32_t if (failed) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ result = MU_FAILURE; } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_021: [ On successful update of the existing job object, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ result = 0; } @@ -214,32 +215,34 @@ static int internal_job_object_helper_create(const char* job_name, uint32_t perc { int result; - /*Codes_SRS_JOB_OBJECT_HELPER_88_035: [ internal_job_object_helper_create shall allocate a JOB_OBJECT_HELPER object. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_035: [ job_object_helper_set_job_limits_to_current_process shall allocate a JOB_OBJECT_HELPER object. ]*/ JOB_OBJECT_HELPER* job_object_helper = THANDLE_MALLOC(JOB_OBJECT_HELPER)(job_object_helper_dispose); if (job_object_helper == NULL) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ LogError("failure in THANDLE_MALLOC(JOB_OBJECT_HELPER)(job_object_helper_dispose=%p)", job_object_helper_dispose); result = MU_FAILURE; } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_036: [ internal_job_object_helper_create shall call CreateJobObjectA passing job_name for lpName and NULL for lpJobAttributes. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_036: [ job_object_helper_set_job_limits_to_current_process shall call CreateJobObjectA passing job_name for lpName and NULL for lpJobAttributes. ]*/ job_object_helper->job_object = CreateJobObjectA(NULL, job_name); if (job_object_helper->job_object == NULL) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ LogLastError("failure in CreateJobObjectA(lpJobAttributes=NULL, job_name=%s)", MU_P_OR_NULL(job_name)); } else { bool failed = false; - /*Codes_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the new job object. ]*/ if(percent_cpu != 0) { if (internal_job_object_helper_set_cpu_limit(job_object_helper->job_object, percent_cpu) != 0) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + LogError("failure in internal_job_object_helper_set_cpu_limit(job_object=%p, percent_cpu=%" PRIu32 ") during creation", + job_object_helper->job_object, percent_cpu); failed = true; } } @@ -254,12 +257,14 @@ static int internal_job_object_helper_create(const char* job_name, uint32_t perc } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the new job object. ]*/ if(percent_physical_memory != 0) { if (internal_job_object_helper_set_memory_limit(job_object_helper->job_object, percent_physical_memory) != 0) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + LogError("failure in internal_job_object_helper_set_memory_limit(job_object=%p, percent_physical_memory=%" PRIu32 ") during creation", + job_object_helper->job_object, percent_physical_memory); failed = true; } else @@ -279,19 +284,19 @@ static int internal_job_object_helper_create(const char* job_name, uint32_t perc } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_039: [ internal_job_object_helper_create shall call GetCurrentProcess to get the current process handle. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_039: [ job_object_helper_set_job_limits_to_current_process shall call GetCurrentProcess to get the current process handle. ]*/ HANDLE current_process = GetCurrentProcess(); - /*Codes_SRS_JOB_OBJECT_HELPER_19_008: [ internal_job_object_helper_create shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_008: [ job_object_helper_set_job_limits_to_current_process shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ if (!AssignProcessToJobObject(job_object_helper->job_object, current_process)) { - /*Codes_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ LogLastError("failure in AssignProcessToJobObject(job_object=%p, current_process=%p)", job_object_helper->job_object, current_process); } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_024: [ On success, internal_job_object_helper_create shall store the THANDLE(JOB_OBJECT_HELPER) in the process-level singleton state. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_024: [ On success, job_object_helper_set_job_limits_to_current_process shall store the THANDLE(JOB_OBJECT_HELPER) in the process-level singleton state. ]*/ THANDLE_INITIALIZE_MOVE(JOB_OBJECT_HELPER)(&job_object_singleton_state.job_object_helper, &job_object_helper); - /*Codes_SRS_JOB_OBJECT_HELPER_88_033: [ internal_job_object_helper_create shall succeed and return 0. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_19_010: [ job_object_helper_set_job_limits_to_current_process shall succeed and return a JOB_OBJECT_HELPER object. ]*/ result = 0; goto all_ok; } @@ -333,24 +338,23 @@ IMPLEMENT_MOCKABLE_FUNCTION(, THANDLE(JOB_OBJECT_HELPER), job_object_helper_set_ percent_cpu, percent_physical_memory); if( - /*Codes_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_041: [ If job_object_singleton_state.job_object_helper is not NULL and percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ (percent_cpu == 0 && job_object_singleton_state.percent_cpu != 0) || - /*Codes_SRS_JOB_OBJECT_HELPER_88_046: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_046: [ If job_object_singleton_state.job_object_helper is not NULL and percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ (percent_physical_memory == 0 && job_object_singleton_state.percent_memory != 0)) { LogError("Invalid arguments: percent_cpu or percent_physical_memory cannot be set to 0 once it has been set to a non-zero value. Received percent_cpu=%" PRIu32 ", received percent_physical_memory=%" PRIu32 ", current percent_cpu=%" PRIu32 ", current percent_physical_memory=%" PRIu32 "", percent_cpu, percent_physical_memory, job_object_singleton_state.percent_cpu, job_object_singleton_state.percent_memory); } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_002: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_reconfigure to apply the limits to the existing job object. ]*/ - if (internal_job_object_helper_reconfigure(percent_cpu, percent_physical_memory) != 0) + if (internal_job_object_helper_update(percent_cpu, percent_physical_memory) != 0) { /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ /* Error already logged */ } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ + /*Codes_SRS_JOB_OBJECT_HELPER_88_021: [ On successful update of the existing job object, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ THANDLE_INITIALIZE(JOB_OBJECT_HELPER)(&result, job_object_singleton_state.job_object_helper); } } @@ -365,7 +369,6 @@ IMPLEMENT_MOCKABLE_FUNCTION(, THANDLE(JOB_OBJECT_HELPER), job_object_helper_set_ } else { - /*Codes_SRS_JOB_OBJECT_HELPER_88_031: [ job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_create to create a new job object and assign it to the current process. ]*/ if (internal_job_object_helper_create(job_name, percent_cpu, percent_physical_memory) != 0) { /*Codes_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ diff --git a/win32/tests/job_object_helper_int/CMakeLists.txt b/win32/tests/job_object_helper_int/CMakeLists.txt index cfd426e3..55544672 100644 --- a/win32/tests/job_object_helper_int/CMakeLists.txt +++ b/win32/tests/job_object_helper_int/CMakeLists.txt @@ -7,7 +7,7 @@ set(int_test_names job_object_helper_memory_int job_object_helper_multi_process_int job_object_helper_singleton_int - job_object_helper_reconfigure_int + job_object_helper_update_int job_object_helper_partial_limits_int ) diff --git a/win32/tests/job_object_helper_int/job_object_helper_negative_int.c b/win32/tests/job_object_helper_int/job_object_helper_negative_int.c index b7c3fc8b..258f665d 100644 --- a/win32/tests/job_object_helper_int/job_object_helper_negative_int.c +++ b/win32/tests/job_object_helper_int/job_object_helper_negative_int.c @@ -158,9 +158,9 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_w } } -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_reconfigure_cpu_from_nonzero_to_zero) +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_update_cpu_from_nonzero_to_zero) { - /* Reconfiguring CPU from a non-zero value to 0 is not allowed. + /* Updating CPU from a non-zero value to 0 is not allowed. Once CPU rate control has been applied, it cannot be removed — pass 100 to effectively disable. */ ///arrange @@ -168,18 +168,18 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_w THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); ///act — try to set cpu to 0 (previously was non-zero) - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 0, INITIAL_MEMORY_PERCENT); + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 0, INITIAL_MEMORY_PERCENT); ///assert - ASSERT_IS_NULL(reconfigured_job_object_helper, "Reconfigure cpu from non-zero to 0 should fail and return NULL"); + ASSERT_IS_NULL(updated_job_object_helper, "Update cpu from non-zero to 0 should fail and return NULL"); ///cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); } -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_reconfigure_memory_from_nonzero_to_zero) +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_when_update_memory_from_nonzero_to_zero) { - /* Reconfiguring memory from a non-zero value to 0 is not allowed. + /* Updating memory from a non-zero value to 0 is not allowed. Once memory limits have been applied, they cannot be removed — pass 100 to effectively disable. */ ///arrange @@ -187,10 +187,10 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_null_w THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); ///act — try to set memory to 0 (previously was non-zero) - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, INITIAL_CPU_PERCENT, 0); + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, INITIAL_CPU_PERCENT, 0); ///assert - ASSERT_IS_NULL(reconfigured_job_object_helper, "Reconfigure memory from non-zero to 0 should fail and return NULL"); + ASSERT_IS_NULL(updated_job_object_helper, "Update memory from non-zero to 0 should fail and return NULL"); ///cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); diff --git a/win32/tests/job_object_helper_int/job_object_helper_partial_limits_int.c b/win32/tests/job_object_helper_int/job_object_helper_partial_limits_int.c index 0e6c51d0..92244a4d 100644 --- a/win32/tests/job_object_helper_int/job_object_helper_partial_limits_int.c +++ b/win32/tests/job_object_helper_int/job_object_helper_partial_limits_int.c @@ -22,8 +22,8 @@ #define TEST_MEMORY_PERCENT 1 #define INITIAL_CPU_PERCENT 50 #define INITIAL_MEMORY_PERCENT 1 -#define RECONFIGURED_CPU_PERCENT 30 -#define RECONFIGURED_MEMORY_PERCENT 2 +#define UPDATED_CPU_PERCENT 30 +#define UPDATED_MEMORY_PERCENT 2 static THANDLE(JOB_OBJECT_HELPER) create_job_object_with_limits(char* job_name_out, size_t job_name_size, uint32_t cpu, uint32_t memory) { @@ -153,74 +153,74 @@ TEST_FUNCTION(job_object_helper_set_job_limits_with_nonzero_cpu_and_zero_memory_ THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&job_object_helper, NULL); } -TEST_FUNCTION(job_object_helper_reconfigure_both_limits_updated_succeeds) +TEST_FUNCTION(job_object_helper_update_both_limits_succeeds) { /* This test verifies that an existing job object created with both CPU and memory limits - can be reconfigured to update both limits: + can be updated to new limits: (50% CPU, 1% memory) -> (30% CPU, 2% memory) */ ///arrange char job_name[64]; THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); - ///act - reconfigure to updated values - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, RECONFIGURED_CPU_PERCENT, RECONFIGURED_MEMORY_PERCENT); + ///act - update to new values + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, UPDATED_CPU_PERCENT, UPDATED_MEMORY_PERCENT); ///assert - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper, "Reconfigure should succeed"); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper, "Reconfigured call should return same singleton"); + ASSERT_IS_NOT_NULL(updated_job_object_helper, "Update should succeed"); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, updated_job_object_helper, "Updated call should return same singleton"); HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); - ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after reconfigure"); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after update"); /* Verify CPU rate control is updated */ JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_info = { 0 }; DWORD return_length = 0; BOOL query_result = QueryInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after reconfigure"); - ASSERT_ARE_EQUAL(uint32_t, (uint32_t)(JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP), cpu_info.ControlFlags, "CPU rate control should be enabled with hard cap after reconfigure"); - ASSERT_ARE_EQUAL(uint32_t, RECONFIGURED_CPU_PERCENT * 100, cpu_info.CpuRate, "Reconfigured CPU rate should be %" PRIu32 "", RECONFIGURED_CPU_PERCENT * 100); - LogInfo("Reconfigured CPU rate verified: ControlFlags=%" PRIu32 ", CpuRate=%" PRIu32 "", cpu_info.ControlFlags, cpu_info.CpuRate); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after update"); + ASSERT_ARE_EQUAL(uint32_t, (uint32_t)(JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP), cpu_info.ControlFlags, "CPU rate control should be enabled with hard cap after update"); + ASSERT_ARE_EQUAL(uint32_t, UPDATED_CPU_PERCENT * 100, cpu_info.CpuRate, "Updated CPU rate should be %" PRIu32 "", UPDATED_CPU_PERCENT * 100); + LogInfo("Updated CPU rate verified: ControlFlags=%" PRIu32 ", CpuRate=%" PRIu32 "", cpu_info.ControlFlags, cpu_info.CpuRate); /* Verify memory limit is updated */ MEMORYSTATUSEX mem_status; mem_status.dwLength = sizeof(mem_status); ASSERT_IS_TRUE(GlobalMemoryStatusEx(&mem_status)); - SIZE_T expected_memory_in_MB = RECONFIGURED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; + SIZE_T expected_memory_in_MB = UPDATED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_info = { 0 }; query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after reconfigure"); - ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Reconfigured job memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)RECONFIGURED_MEMORY_PERCENT); - ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.ProcessMemoryLimit / MEGABYTE, "Reconfigured process memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)RECONFIGURED_MEMORY_PERCENT); - LogInfo("Reconfigured memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after update"); + ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Updated job memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)UPDATED_MEMORY_PERCENT); + ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.ProcessMemoryLimit / MEGABYTE, "Updated process memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)UPDATED_MEMORY_PERCENT); + LogInfo("Updated memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); (void)CloseHandle(job_object); ///cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); } -TEST_FUNCTION(job_object_helper_reconfigure_memory_only_when_cpu_was_zero_succeeds) +TEST_FUNCTION(job_object_helper_update_memory_only_when_cpu_was_zero_succeeds) { /* This test verifies that when the initial creation had cpu=0 (CPU not applied), - a reconfigure with cpu=0 (still not applied) and updated memory succeeds. + an update with cpu=0 (still not applied) and updated memory succeeds. (0% CPU, 1% memory) -> (0% CPU, 2% memory) */ ///arrange char job_name[64]; THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), 0, INITIAL_MEMORY_PERCENT); - ///act - reconfigure: cpu still 0, memory updated - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 0, RECONFIGURED_MEMORY_PERCENT); + ///act - update: cpu still 0, memory updated + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, 0, UPDATED_MEMORY_PERCENT); ///assert - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper, "Reconfigure should succeed"); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper, "Reconfigured call should return same singleton"); + ASSERT_IS_NOT_NULL(updated_job_object_helper, "Update should succeed"); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, updated_job_object_helper, "Updated call should return same singleton"); HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); - ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after reconfigure"); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after update"); /* Verify CPU rate control is still not applied */ JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_info = { 0 }; @@ -234,54 +234,54 @@ TEST_FUNCTION(job_object_helper_reconfigure_memory_only_when_cpu_was_zero_succee MEMORYSTATUSEX mem_status; mem_status.dwLength = sizeof(mem_status); ASSERT_IS_TRUE(GlobalMemoryStatusEx(&mem_status)); - SIZE_T expected_memory_in_MB = RECONFIGURED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; + SIZE_T expected_memory_in_MB = UPDATED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_info = { 0 }; query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after reconfigure"); - ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Reconfigured job memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)RECONFIGURED_MEMORY_PERCENT); - LogInfo("Reconfigured memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after update"); + ASSERT_ARE_EQUAL(size_t, expected_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Updated job memory limit should be %" PRIu32 "%% of total physical memory", (uint32_t)UPDATED_MEMORY_PERCENT); + LogInfo("Updated memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); (void)CloseHandle(job_object); ///cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); } -TEST_FUNCTION(job_object_helper_reconfigure_cpu_only_when_memory_was_zero_succeeds) +TEST_FUNCTION(job_object_helper_update_cpu_only_when_memory_was_zero_succeeds) { /* This test verifies that when the initial creation had memory=0 (memory not applied), - a reconfigure with memory=0 (still not applied) and updated CPU succeeds. + an update with memory=0 (still not applied) and updated CPU succeeds. (50% CPU, 0% memory) -> (30% CPU, 0% memory) */ ///arrange char job_name[64]; THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_with_limits(job_name, sizeof(job_name), INITIAL_CPU_PERCENT, 0); - ///act - reconfigure: cpu updated, memory still 0 - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, RECONFIGURED_CPU_PERCENT, 0); + ///act - update: cpu updated, memory still 0 + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, UPDATED_CPU_PERCENT, 0); ///assert - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper, "Reconfigure should succeed"); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper, "Reconfigured call should return same singleton"); + ASSERT_IS_NOT_NULL(updated_job_object_helper, "Update should succeed"); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, updated_job_object_helper, "Updated call should return same singleton"); HANDLE job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); - ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after reconfigure"); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after update"); /* Verify CPU rate control is updated */ JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpu_info = { 0 }; DWORD return_length = 0; BOOL query_result = QueryInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after reconfigure"); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after update"); ASSERT_ARE_EQUAL(uint32_t, (uint32_t)(JOB_OBJECT_CPU_RATE_CONTROL_ENABLE | JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP), cpu_info.ControlFlags, "CPU rate control should be enabled with hard cap"); - ASSERT_ARE_EQUAL(uint32_t, RECONFIGURED_CPU_PERCENT * 100, cpu_info.CpuRate, "Reconfigured CPU rate should be %" PRIu32 "", RECONFIGURED_CPU_PERCENT * 100); - LogInfo("Reconfigured CPU rate verified: ControlFlags=%" PRIu32 ", CpuRate=%" PRIu32 "", cpu_info.ControlFlags, cpu_info.CpuRate); + ASSERT_ARE_EQUAL(uint32_t, UPDATED_CPU_PERCENT * 100, cpu_info.CpuRate, "Updated CPU rate should be %" PRIu32 "", UPDATED_CPU_PERCENT * 100); + LogInfo("Updated CPU rate verified: ControlFlags=%" PRIu32 ", CpuRate=%" PRIu32 "", cpu_info.ControlFlags, cpu_info.CpuRate); /* Verify memory limit is still not applied */ JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_info = { 0 }; query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after reconfigure"); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after update"); ASSERT_ARE_EQUAL(uint32_t, (uint32_t)0, ext_info.BasicLimitInformation.LimitFlags & (JOB_OBJECT_LIMIT_JOB_MEMORY | JOB_OBJECT_LIMIT_PROCESS_MEMORY), "Memory limit flags should still not be set"); LogInfo("Memory limits verified as still not applied (LimitFlags=0x%08lx)", (unsigned long)ext_info.BasicLimitInformation.LimitFlags); @@ -289,7 +289,7 @@ TEST_FUNCTION(job_object_helper_reconfigure_cpu_only_when_memory_was_zero_succee ///cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); } END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) diff --git a/win32/tests/job_object_helper_int/job_object_helper_reconfigure_int.c b/win32/tests/job_object_helper_int/job_object_helper_update_int.c similarity index 65% rename from win32/tests/job_object_helper_int/job_object_helper_reconfigure_int.c rename to win32/tests/job_object_helper_int/job_object_helper_update_int.c index edf8a315..c6071d79 100644 --- a/win32/tests/job_object_helper_int/job_object_helper_reconfigure_int.c +++ b/win32/tests/job_object_helper_int/job_object_helper_update_int.c @@ -24,11 +24,11 @@ struct JOB_OBJECT_HELPER_TAG { #define MEGABYTE ((size_t)1024 * 1024) -#define TEST_JOB_NAME_PREFIX "job_test_reconfig_" +#define TEST_JOB_NAME_PREFIX "job_test_update_" #define INITIAL_CPU_PERCENT 50 #define INITIAL_MEMORY_PERCENT 1 -#define RECONFIGURED_CPU_PERCENT 30 -#define RECONFIGURED_MEMORY_PERCENT 2 +#define UPDATED_CPU_PERCENT 30 +#define UPDATED_MEMORY_PERCENT 2 static THANDLE(JOB_OBJECT_HELPER) create_job_object_with_limits(char* job_name_out, size_t job_name_size, uint32_t cpu, uint32_t memory) { @@ -61,12 +61,12 @@ TEST_FUNCTION_CLEANUP(cleanup) job_object_helper_deinit_for_test(); } -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_limits_for_named_job_object) +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_updates_limits_for_named_job_object) { /* This test verifies that: * 1. A job object is created with initial limits - * 2. The limits can be reconfigured in-place via a second call - * 3. The reconfigured limits are applied to the existing job object + * 2. The limits can be updated in-place via a second call + * 3. The updated limits are applied to the existing job object */ // Step 1: Create the singleton with initial limits (50% CPU, 1% memory) @@ -98,45 +98,45 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_l (void)CloseHandle(job_object); - // Step 2: Reconfigure to new limits (30% CPU, 2% memory) - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, RECONFIGURED_CPU_PERCENT, RECONFIGURED_MEMORY_PERCENT); - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper, "Reconfigure should succeed"); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper, "Reconfigured call should return same singleton"); + // Step 2: Update to new limits (30% CPU, 2% memory) + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process(job_name, UPDATED_CPU_PERCENT, UPDATED_MEMORY_PERCENT); + ASSERT_IS_NOT_NULL(updated_job_object_helper, "Update should succeed"); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, updated_job_object_helper, "Updated call should return same singleton"); - // Step 3: Verify reconfigured CPU rate + // Step 3: Verify updated CPU rate job_object = OpenJobObjectA(JOB_OBJECT_QUERY, FALSE, job_name); - ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after reconfigure"); + ASSERT_IS_NOT_NULL(job_object, "Failed to open job object after update"); (void)memset(&cpu_info, 0, sizeof(cpu_info)); query_result = QueryInformationJobObject(job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after reconfigure"); - ASSERT_ARE_EQUAL(uint32_t, RECONFIGURED_CPU_PERCENT * 100, cpu_info.CpuRate, "Reconfigured CPU rate should be %" PRIu32 "", RECONFIGURED_CPU_PERCENT * 100); - LogInfo("Reconfigured CPU rate verified: %" PRIu32 "", cpu_info.CpuRate); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after update"); + ASSERT_ARE_EQUAL(uint32_t, UPDATED_CPU_PERCENT * 100, cpu_info.CpuRate, "Updated CPU rate should be %" PRIu32 "", UPDATED_CPU_PERCENT * 100); + LogInfo("Updated CPU rate verified: %" PRIu32 "", cpu_info.CpuRate); - // Verify reconfigured memory limit - SIZE_T expected_reconfigured_memory_in_MB = RECONFIGURED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; + // Verify updated memory limit + SIZE_T expected_updated_memory_in_MB = UPDATED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; (void)memset(&ext_info, 0, sizeof(ext_info)); query_result = QueryInformationJobObject(job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after reconfigure"); - ASSERT_ARE_EQUAL(size_t, expected_reconfigured_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Reconfigured job memory limit should match"); - LogInfo("Reconfigured memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after update"); + ASSERT_ARE_EQUAL(size_t, expected_updated_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Updated job memory limit should match"); + LogInfo("Updated memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); (void)CloseHandle(job_object); // Cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); } -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_limits_for_unnamed_job_object) +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_updates_limits_for_unnamed_job_object) { - /* This test verifies reconfiguration works for unnamed job objects (NULL job_name). + /* This test verifies update works for unnamed job objects (NULL job_name). * Since OpenJobObjectA cannot be used with unnamed objects, we access the * internal job_object HANDLE from the THANDLE to query limits directly. */ - LogInfo("Running reconfigure test with NULL job name (unnamed job object)..."); + LogInfo("Running update test with NULL job name (unnamed job object)..."); // Step 1: Create the singleton with initial limits THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = job_object_helper_set_job_limits_to_current_process(NULL, INITIAL_CPU_PERCENT, INITIAL_MEMORY_PERCENT); @@ -162,30 +162,30 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_l ASSERT_ARE_EQUAL(size_t, expected_initial_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Initial job memory limit should match"); LogInfo("Initial memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); - // Step 2: Reconfigure to new limits - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process(NULL, RECONFIGURED_CPU_PERCENT, RECONFIGURED_MEMORY_PERCENT); - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper, "Reconfigure should succeed"); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper, "Reconfigured call should return same singleton"); + // Step 2: Update to new limits + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process(NULL, UPDATED_CPU_PERCENT, UPDATED_MEMORY_PERCENT); + ASSERT_IS_NOT_NULL(updated_job_object_helper, "Update should succeed"); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, updated_job_object_helper, "Updated call should return same singleton"); - // Step 3: Verify reconfigured CPU rate + // Step 3: Verify updated CPU rate (void)memset(&cpu_info, 0, sizeof(cpu_info)); - query_result = QueryInformationJobObject(reconfigured_job_object_helper->job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after reconfigure"); - ASSERT_ARE_EQUAL(uint32_t, RECONFIGURED_CPU_PERCENT * 100, cpu_info.CpuRate, "Reconfigured CPU rate should be %" PRIu32 "", RECONFIGURED_CPU_PERCENT * 100); - LogInfo("Reconfigured CPU rate verified: %" PRIu32 "", cpu_info.CpuRate); + query_result = QueryInformationJobObject(updated_job_object_helper->job_object, JobObjectCpuRateControlInformation, &cpu_info, sizeof(cpu_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query CPU rate info after update"); + ASSERT_ARE_EQUAL(uint32_t, UPDATED_CPU_PERCENT * 100, cpu_info.CpuRate, "Updated CPU rate should be %" PRIu32 "", UPDATED_CPU_PERCENT * 100); + LogInfo("Updated CPU rate verified: %" PRIu32 "", cpu_info.CpuRate); - // Verify reconfigured memory limit - SIZE_T expected_reconfigured_memory_in_MB = RECONFIGURED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; + // Verify updated memory limit + SIZE_T expected_updated_memory_in_MB = UPDATED_MEMORY_PERCENT * mem_status.ullTotalPhys / 100 / MEGABYTE; (void)memset(&ext_info, 0, sizeof(ext_info)); - query_result = QueryInformationJobObject(reconfigured_job_object_helper->job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); - ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after reconfigure"); - ASSERT_ARE_EQUAL(size_t, expected_reconfigured_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Reconfigured job memory limit should match"); - LogInfo("Reconfigured memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); + query_result = QueryInformationJobObject(updated_job_object_helper->job_object, JobObjectExtendedLimitInformation, &ext_info, sizeof(ext_info), &return_length); + ASSERT_IS_TRUE(query_result, "Failed to query extended limit info after update"); + ASSERT_ARE_EQUAL(size_t, expected_updated_memory_in_MB, ext_info.JobMemoryLimit / MEGABYTE, "Updated job memory limit should match"); + LogInfo("Updated memory limit verified: %zu MB", ext_info.JobMemoryLimit / MEGABYTE); // Cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); } END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) diff --git a/win32/tests/job_object_helper_ut/job_object_helper_ut.c b/win32/tests/job_object_helper_ut/job_object_helper_ut.c index 863c9f04..0ad86d2f 100644 --- a/win32/tests/job_object_helper_ut/job_object_helper_ut.c +++ b/win32/tests/job_object_helper_ut/job_object_helper_ut.c @@ -205,23 +205,21 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_test_limits) } } -/*Tests_SRS_JOB_OBJECT_HELPER_88_031: [ job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_create to create a new job object and assign it to the current process. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_035: [ internal_job_object_helper_create shall allocate a JOB_OBJECT_HELPER object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_036: [ internal_job_object_helper_create shall call CreateJobObjectA passing job_name for lpName and NULL for lpJobAttributes.]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_035: [ job_object_helper_set_job_limits_to_current_process shall allocate a JOB_OBJECT_HELPER object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_036: [ job_object_helper_set_job_limits_to_current_process shall call CreateJobObjectA passing job_name for lpName and NULL for lpJobAttributes. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_005: [ internal_job_object_helper_set_cpu_limit shall set ControlFlags to JOB_OBJECT_CPU_RATE_CONTROL_ENABLE and JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, and CpuRate to percent_cpu times 100. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_006: [ internal_job_object_helper_set_cpu_limit shall call SetInformationJobObject passing JobObjectCpuRateControlInformation and the JOBOBJECT_CPU_RATE_CONTROL_INFORMATION. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_042: [ On success, internal_job_object_helper_set_cpu_limit shall store percent_cpu in the singleton state. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_011: [ internal_job_object_helper_set_memory_limit shall call GlobalMemoryStatusEx to get the total amount of physical memory. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_012: [ internal_job_object_helper_set_memory_limit shall set JobMemoryLimit and ProcessMemoryLimit to percent_physical_memory percent of the physical memory and call SetInformationJobObject with JobObjectExtendedLimitInformation. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_043: [ On success, internal_job_object_helper_set_memory_limit shall store percent_physical_memory in the singleton state. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_039: [ internal_job_object_helper_create shall call GetCurrentProcess to get the current process handle. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_19_008: [ internal_job_object_helper_create shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_039: [ job_object_helper_set_job_limits_to_current_process shall call GetCurrentProcess to get the current process handle. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_19_008: [ job_object_helper_set_job_limits_to_current_process shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_008: [ internal_job_object_helper_set_cpu_limit shall succeed and return 0. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_014: [ internal_job_object_helper_set_memory_limit shall succeed and return 0. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_024: [ On success, internal_job_object_helper_create shall store the THANDLE(JOB_OBJECT_HELPER) in the process-level singleton state. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_033: [ internal_job_object_helper_create shall succeed and return 0. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_024: [ On success, job_object_helper_set_job_limits_to_current_process shall store the THANDLE(JOB_OBJECT_HELPER) in the process-level singleton state. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_19_010: [ job_object_helper_set_job_limits_to_current_process shall succeed and return a JOB_OBJECT_HELPER object. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_succeeds) { @@ -270,17 +268,15 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_w } /*Tests_SRS_JOB_OBJECT_HELPER_88_030: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall not create a new job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_002: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_reconfigure to apply the limits to the existing job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If percent_cpu is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If percent_physical_memory is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_with_same_params) +/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If job_object_singleton_state.job_object_helper is not NULL and percent_cpu is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the existing job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If job_object_singleton_state.job_object_helper is not NULL and percent_physical_memory is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the existing job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ On successful update of the existing job object, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_updates_with_same_params) { // arrange - create singleton THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); - // expect reconfigure calls (idempotent: same values applied again) + // expect update calls (idempotent: same values applied again) setup_job_object_helper_limit_cpu_expectations(); setup_job_object_helper_limit_memory_expectations(); @@ -297,51 +293,51 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_w THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reused_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_reconfigure_cpu_from_nonzero_to_zero) +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ If job_object_singleton_state.job_object_helper is not NULL and percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_update_cpu_from_nonzero_to_zero) { // arrange - create singleton with (50, 50) THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); - // act - reconfigure cpu from 50->0 should fail (SRS_88_041) - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 0, 50); + // act - update cpu from 50->0 should fail (SRS_88_041) + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 0, 50); // assert - should fail because percent_cpu was non-zero (50) and now being set to 0 - ASSERT_IS_NULL(reconfigured_job_object_helper); + ASSERT_IS_NULL(updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); // cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_reconfigure_memory_from_nonzero_to_zero) +/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ If job_object_singleton_state.job_object_helper is not NULL and percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_update_memory_from_nonzero_to_zero) { // arrange - create singleton with (50, 50) THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); - // act - reconfigure memory from 50->0 should fail (SRS_88_041) - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 0); + // act - update memory from 50->0 should fail (SRS_88_046) + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 0); // assert - should fail because percent_memory was non-zero (50) and now being set to 0 - ASSERT_IS_NULL(reconfigured_job_object_helper); + ASSERT_IS_NULL(updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); // cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ -TEST_FUNCTION(when_created_with_cpu_0_memory_50_then_reconfigure_cpu_50_memory_0_fails) +/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ If job_object_singleton_state.job_object_helper is not NULL and percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(when_created_with_cpu_0_memory_50_then_update_cpu_50_memory_0_fails) { // arrange - create singleton with cpu=0, memory=50 THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(0, 50); - // act - reconfigure to cpu=50, memory=0. memory was 50 (non-zero) so setting to 0 should fail - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 0); + // act - update to cpu=50, memory=0. memory was 50 (non-zero) so setting to 0 should fail + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 0); // assert - should fail because percent_memory was non-zero (50) and now being set to 0 - ASSERT_IS_NULL(reconfigured_job_object_helper); + ASSERT_IS_NULL(updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); // cleanup @@ -349,80 +345,78 @@ TEST_FUNCTION(when_created_with_cpu_0_memory_50_then_reconfigure_cpu_50_memory_0 } /*Tests_SRS_JOB_OBJECT_HELPER_88_042: [ On success, internal_job_object_helper_set_cpu_limit shall store percent_cpu in the singleton state. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ During reconfiguration, if percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ -TEST_FUNCTION(when_reconfigure_sets_cpu_then_state_is_updated_and_subsequent_zero_cpu_fails) +/*Tests_SRS_JOB_OBJECT_HELPER_88_041: [ If job_object_singleton_state.job_object_helper is not NULL and percent_cpu is 0 and the job_object_singleton_state.percent_cpu is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(when_update_sets_cpu_then_state_is_updated_and_subsequent_zero_cpu_fails) { // arrange - create singleton with cpu=0, memory=50 (only memory limit) THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(0, 50); - // Reconfigure to (30, 50) — this should store percent_cpu=30 in singleton state + // Update to (30, 50) — this should store percent_cpu=30 in singleton state setup_job_object_helper_limit_cpu_expectations(); setup_job_object_helper_limit_memory_expectations(); - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 50); - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper); + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 50); + ASSERT_IS_NOT_NULL(updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - // act - try to reconfigure cpu back to 0. If state was updated, percent_cpu is now 30 + // act - try to update cpu back to 0. If state was updated, percent_cpu is now 30 // and this should fail (SRS_88_041). If state was NOT updated, percent_cpu would still // be 0 and this would incorrectly succeed. THANDLE(JOB_OBJECT_HELPER) zero_cpu_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 0, 50); - // assert - must fail because percent_cpu was stored as 30 during reconfiguration + // assert - must fail because percent_cpu was stored as 30 during update ASSERT_IS_NULL(zero_cpu_job_object_helper); // cleanup - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); } /*Tests_SRS_JOB_OBJECT_HELPER_88_043: [ On success, internal_job_object_helper_set_memory_limit shall store percent_physical_memory in the singleton state. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ During reconfiguration, if percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ -TEST_FUNCTION(when_reconfigure_sets_memory_then_state_is_updated_and_subsequent_zero_memory_fails) +/*Tests_SRS_JOB_OBJECT_HELPER_88_046: [ If job_object_singleton_state.job_object_helper is not NULL and percent_physical_memory is 0 and the job_object_singleton_state.percent_memory is non-zero, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ +TEST_FUNCTION(when_update_sets_memory_then_state_is_updated_and_subsequent_zero_memory_fails) { // arrange - create singleton with cpu=50, memory=0 (only CPU limit) THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 0); - // Reconfigure to (50, 30) — this should store percent_memory=30 in singleton state + // Update to (50, 30) — this should store percent_memory=30 in singleton state setup_job_object_helper_limit_cpu_expectations(); setup_job_object_helper_limit_memory_expectations(); - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 30); - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper); + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 30); + ASSERT_IS_NOT_NULL(updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - // act - try to reconfigure memory back to 0. If state was updated, percent_memory is - // now 30 and this should fail (SRS_88_041). If state was NOT updated, percent_memory + // act - try to update memory back to 0. If state was updated, percent_memory is + // now 30 and this should fail (SRS_88_046). If state was NOT updated, percent_memory // would still be 0 and this would incorrectly succeed. THANDLE(JOB_OBJECT_HELPER) zero_memory_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 50, 0); - // assert - must fail because percent_memory was stored as 30 during reconfiguration + // assert - must fail because percent_memory was stored as 30 during update ASSERT_IS_NULL(zero_memory_job_object_helper); // cleanup - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); } /*Tests_SRS_JOB_OBJECT_HELPER_88_030: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall not create a new job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_002: [ If job_object_singleton_state.job_object_helper is not NULL, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_reconfigure to apply the limits to the existing job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If percent_cpu is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If percent_physical_memory is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_when_params_change) +/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If job_object_singleton_state.job_object_helper is not NULL and percent_cpu is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the existing job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If job_object_singleton_state.job_object_helper is not NULL and percent_physical_memory is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the existing job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ On successful update of the existing job object, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_updates_when_params_change) { // arrange - create singleton with (50, 50) THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); - // expect reconfigure calls: CPU limit change + memory limit change + // expect update calls: CPU limit change + memory limit change setup_job_object_helper_limit_cpu_expectations(); setup_job_object_helper_limit_memory_expectations(); - // act - reconfigure to (30, 30) - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 30); + // act - update to (30, 30) + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 30); // assert - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper); + ASSERT_IS_NOT_NULL(updated_job_object_helper); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); // verify the captured CPU rate matches the new value (30 * 100 = 3000) @@ -434,28 +428,27 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_w // cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If percent_cpu is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If percent_physical_memory is not 0, internal_job_object_helper_reconfigure shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_020: [ On successful reconfiguration, internal_job_object_helper_reconfigure shall return 0. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ If internal_job_object_helper_reconfigure returns 0, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_to_100_100) +/*Tests_SRS_JOB_OBJECT_HELPER_88_003: [ If job_object_singleton_state.job_object_helper is not NULL and percent_cpu is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the existing job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_009: [ If job_object_singleton_state.job_object_helper is not NULL and percent_physical_memory is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the existing job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_021: [ On successful update of the existing job object, job_object_helper_set_job_limits_to_current_process shall increment the reference count on the existing THANDLE(JOB_OBJECT_HELPER) and return it. ]*/ +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_updates_to_100_100) { // arrange - create singleton with (50, 50) THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); - // expect reconfigure calls: CPU limit change + memory limit change + // expect update calls: CPU limit change + memory limit change setup_job_object_helper_limit_cpu_expectations(); setup_job_object_helper_limit_memory_expectations(); - // act - reconfigure to (100, 100) — effectively no limits but allowed for existing singleton - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 100, 100); + // act - update to (100, 100) — effectively no limits but allowed for existing singleton + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 100, 100); // assert - ASSERT_IS_NOT_NULL(reconfigured_job_object_helper); - ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, reconfigured_job_object_helper); + ASSERT_IS_NOT_NULL(updated_job_object_helper); + ASSERT_ARE_EQUAL(void_ptr, initial_job_object_helper, updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); // verify CpuRate=10000 (100 * 100) and ControlFlags have ENABLE+HARD_CAP @@ -468,13 +461,12 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_reconfigures_t // cleanup THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&initial_job_object_helper, NULL); - THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&reconfigured_job_object_helper, NULL); + THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&updated_job_object_helper, NULL); } /*Tests_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_007: [ If SetInformationJobObject fails, internal_job_object_helper_set_cpu_limit shall fail and return a non-zero value. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_cpu_reconfigure_fails) +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_cpu_update_fails) { // arrange - create singleton with (50, 50) THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); @@ -483,11 +475,11 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_w STRICT_EXPECTED_CALL(mocked_SetInformationJobObject(IGNORED_ARG, JobObjectCpuRateControlInformation, IGNORED_ARG, sizeof(JOBOBJECT_CPU_RATE_CONTROL_INFORMATION))) .SetReturn(FALSE); - // act - reconfigure to (30, 30) but CPU reconfigure fails - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 30); + // act - update to (30, 30) but CPU update fails + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 30); // assert - ASSERT_IS_NULL(reconfigured_job_object_helper); + ASSERT_IS_NULL(updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); // cleanup @@ -496,27 +488,26 @@ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_w /*Tests_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_013: [ If there are any failures, internal_job_object_helper_set_memory_limit shall fail and return a non-zero value. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_017: [ If there are any failures, internal_job_object_helper_reconfigure shall fail and return a non-zero value. ]*/ -TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_memory_reconfigure_fails) +TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_returns_NULL_when_memory_update_fails) { // arrange - create singleton with (50, 50) THANDLE(JOB_OBJECT_HELPER) initial_job_object_helper = create_job_object_helper_singleton(50, 50); - // expect CPU reconfigure succeeds, but memory GlobalMemoryStatusEx fails + // expect CPU update succeeds, but memory GlobalMemoryStatusEx fails setup_job_object_helper_limit_cpu_expectations(); STRICT_EXPECTED_CALL(mocked_GlobalMemoryStatusEx(IGNORED_ARG)) .SetReturn(FALSE); - // act - reconfigure to (30, 30) but memory reconfigure fails - THANDLE(JOB_OBJECT_HELPER) reconfigured_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 30); + // act - update to (30, 30) but memory update fails + THANDLE(JOB_OBJECT_HELPER) updated_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 30); // assert - returns NULL - ASSERT_IS_NULL(reconfigured_job_object_helper); + ASSERT_IS_NULL(updated_job_object_helper); ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); umock_c_reset_all_calls(); - // assert - verify that after partial failure, a subsequent reconfigure still succeeds - // A subsequent call with (30, 50) reconfigures and succeeds + // assert - verify that after partial failure, a subsequent update still succeeds + // A subsequent call with (30, 50) updates and succeeds setup_job_object_helper_limit_cpu_expectations(); setup_job_object_helper_limit_memory_expectations(); THANDLE(JOB_OBJECT_HELPER) subsequent_job_object_helper = job_object_helper_set_job_limits_to_current_process("job_name", 30, 50); @@ -554,20 +545,18 @@ TEST_FUNCTION(job_object_helper_deinit_for_test_resets_singleton) THANDLE_ASSIGN(JOB_OBJECT_HELPER)(&new_job_object_helper, NULL); } -/*Tests_SRS_JOB_OBJECT_HELPER_88_031: [ job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_create to create a new job object and assign it to the current process. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_035: [ internal_job_object_helper_create shall allocate a JOB_OBJECT_HELPER object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_036: [ internal_job_object_helper_create shall call CreateJobObjectA passing job_name for lpName and NULL for lpJobAttributes.]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_035: [ job_object_helper_set_job_limits_to_current_process shall allocate a JOB_OBJECT_HELPER object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_036: [ job_object_helper_set_job_limits_to_current_process shall call CreateJobObjectA passing job_name for lpName and NULL for lpJobAttributes. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_037: [ If percent_cpu is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_cpu_limit to apply the CPU rate control to the new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_005: [ internal_job_object_helper_set_cpu_limit shall set ControlFlags to JOB_OBJECT_CPU_RATE_CONTROL_ENABLE and JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP, and CpuRate to percent_cpu times 100. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_006: [ internal_job_object_helper_set_cpu_limit shall call SetInformationJobObject passing JobObjectCpuRateControlInformation and the JOBOBJECT_CPU_RATE_CONTROL_INFORMATION. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_007: [ If SetInformationJobObject fails, internal_job_object_helper_set_cpu_limit shall fail and return a non-zero value. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, internal_job_object_helper_create shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the Windows job object. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_038: [ If percent_physical_memory is not 0, job_object_helper_set_job_limits_to_current_process shall call internal_job_object_helper_set_memory_limit to apply the memory limit to the new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_011: [ internal_job_object_helper_set_memory_limit shall call GlobalMemoryStatusEx to get the total amount of physical memory. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_012: [ internal_job_object_helper_set_memory_limit shall set JobMemoryLimit and ProcessMemoryLimit to percent_physical_memory percent of the physical memory and call SetInformationJobObject with JobObjectExtendedLimitInformation. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_88_013: [ If there are any failures, internal_job_object_helper_set_memory_limit shall fail and return a non-zero value. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_039: [ internal_job_object_helper_create shall call GetCurrentProcess to get the current process handle. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_19_008: [ internal_job_object_helper_create shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ -/*Tests_SRS_JOB_OBJECT_HELPER_88_032: [ If there are any failures, internal_job_object_helper_create shall fail and return a non-zero value. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_88_039: [ job_object_helper_set_job_limits_to_current_process shall call GetCurrentProcess to get the current process handle. ]*/ +/*Tests_SRS_JOB_OBJECT_HELPER_19_008: [ job_object_helper_set_job_limits_to_current_process shall call AssignProcessToJobObject to assign the current process to the new job object. ]*/ /*Tests_SRS_JOB_OBJECT_HELPER_19_009: [ If there are any failures, job_object_helper_set_job_limits_to_current_process shall fail and return NULL. ]*/ TEST_FUNCTION(job_object_helper_set_job_limits_to_current_process_fails) {