static unsafe void SetNumaAffinity(Win32Job job, SessionSettings session, ulong systemAffinityMask)
        {
            if (session.NumaNode != 0xffff)
            {
                CheckWin32Result(PInvoke.GetNumaNodeProcessorMaskEx(session.NumaNode, out var affinity));

                /*
                 * NUMA node processor mask looks a bit strange (at least to me). In my test Hyper-V environment,
                 * I set the number of NUMA nodes to four with two cores per node. The results of running the following
                 * loop are presented below:
                 *
                 * GetNumaHighestNodeNumber(out var highestNode);
                 *
                 * for (ulong i = 0; i <= highestNode; i++) {
                 *  Console.WriteLine($"Node: {i:X}");
                 *  GetNumaNodeProcessorMaskEx((ushort)i, out var affinity);
                 *  $"Mask: {affinity.Mask:X}".Dump();
                 * }
                 *
                 * Results:
                 *
                 * Node: 0
                 * Mask: 3
                 * Node: 1
                 * Mask: 12
                 * Node: 2
                 * Mask: 48
                 * Node: 3
                 * Mask: 192
                 */

                var nodeAffinityMask = Convert.ToUInt64(affinity.Mask);
                if (session.CpuAffinityMask != 0)
                {
                    // When CPU affinity is set, we can't simply use
                    // NUMA affinity, but rather need to apply the CPU affinity
                    // settings to the select NUMA node.
                    var firstNonZeroBitPosition = 0;
                    while ((nodeAffinityMask & (1UL << firstNonZeroBitPosition)) == 0)
                    {
                        firstNonZeroBitPosition++;
                    }
                    session.CpuAffinityMask <<= firstNonZeroBitPosition & 0x3f;
                }
                else
                {
                    session.CpuAffinityMask = nodeAffinityMask;
                }
                // NOTE: this could result in an overflow on 32-bit apps, but I can't
                // think of a nice way of handling it here
                affinity.Mask = new UIntPtr(session.CpuAffinityMask & systemAffinityMask);

                var size = (uint)Marshal.SizeOf(affinity);
                CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectGroupInformationEx,
                                                                 &affinity, size));
            }
        }
        public static unsafe int WaitForTheJobToComplete(Win32Job job, CancellationToken ct)
        {
            var shouldTerminate = false;

            while (!shouldTerminate && !ct.IsCancellationRequested)
            {
                switch (PInvoke.WaitForSingleObject(job.JobHandle, 200 /* ms */))
                {
                case PInvoke.WAIT_OBJECT_0:
                    logger.TraceEvent(TraceEventType.Information, 0, "End of job time limit passed - terminating.");
                    shouldTerminate = true;
                    break;

                case (uint)WIN32_ERROR.WAIT_FAILED:
                    throw new Win32Exception();

                default:
                    JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jobBasicAcctInfo;
                    uint length;
                    CheckWin32Result(PInvoke.QueryInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectBasicAccountingInformation,
                                                                       &jobBasicAcctInfo, (uint)Marshal.SizeOf <JOBOBJECT_BASIC_ACCOUNTING_INFORMATION>(), &length));
                    Debug.Assert((uint)Marshal.SizeOf <JOBOBJECT_BASIC_ACCOUNTING_INFORMATION>() == length);

                    if (jobBasicAcctInfo.ActiveProcesses == 0)
                    {
                        logger.TraceEvent(TraceEventType.Information, 0, "No active processes in the job - terminating.");
                        shouldTerminate = true;
                    }
                    else if (job.IsTimedOut)
                    {
                        logger.TraceEvent(TraceEventType.Information, 0, "Clock time limit passed - terminating.");
                        PInvoke.TerminateJobObject(job.JobHandle, 1);
                        shouldTerminate = true;
                    }
                    break;
                }
            }

            // Get the exit code and return it
            if (PInvoke.GetExitCodeProcess(job.ProcessHandle, out var masterProcessExitCode))
            {
                // could be STILL_ACTIVE if the process is still running
                return((int)masterProcessExitCode);
            }

            Debug.Fail("Getting the exit code from a process was unsuccessful");
            return(1);
        }
 static unsafe void SetMaxBandwith(Win32Job job, SessionSettings session)
 {
     if (session.MaxBandwidth > 0)
     {
         var limitInfo = new JOBOBJECT_NET_RATE_CONTROL_INFORMATION
         {
             ControlFlags = JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE |
                            JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_MAX_BANDWIDTH,
             MaxBandwidth = session.MaxBandwidth,
             DscpTag      = 0
         };
         var size = (uint)Marshal.SizeOf(limitInfo);
         CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectNetRateControlInformation,
                                                          &limitInfo, size));
     }
 }
        public static unsafe bool TryOpen(SafeHandle hProcess, uint processId, bool propagateOnChildProcesses,
                                          long clockTimeLimitInMilliseconds, out Win32Job?job)
        {
            var hJob = PInvoke.OpenJobObject(PInvoke.JOB_OBJECT_QUERY | PInvoke.JOB_OBJECT_SET_ATTRIBUTES |
                                             PInvoke.JOB_OBJECT_TERMINATE | (uint)FILE_ACCESS_FLAGS.SYNCHRONIZE, false, $"Local\\procgov-{processId}");

            if (hJob.IsInvalid)
            {
                job = null;
                return(false);
            }
            else
            {
                job = new Win32Job(hJob, hProcess, propagateOnChildProcesses, clockTimeLimitInMilliseconds);
                return(true);
            }
        }
        public static void SetLimits(Win32Job job, SessionSettings session)
        {
            if (session.NumaNode != 0xffff)
            {
                CheckWin32Result(PInvoke.GetNumaHighestNodeNumber(out var highestNodeNumber));
                if (session.NumaNode > highestNodeNumber)
                {
                    throw new ArgumentException($"Incorrect NUMA node number. The highest accepted number is {highestNodeNumber}.");
                }
            }

            CheckWin32Result(PInvoke.GetProcessAffinityMask(job.ProcessHandle, out _, out var aff));
            var systemAffinityMask = (ulong)aff;

            SetBasicLimits(job, session, systemAffinityMask);
            SetMaxCpuRate(job, session, systemAffinityMask);
            SetNumaAffinity(job, session, systemAffinityMask);
            SetMaxBandwith(job, session);
        }
        private static unsafe void SetMaxCpuRate(Win32Job job, SessionSettings session, ulong systemAffinityMask)
        {
            if (session.CpuMaxRate > 0)
            {
                // Set CpuRate to a percentage times 100. For example, to let the job use 20% of the CPU,
                // set CpuRate to 20 times 100, or 2,000.
                uint finalCpuRate = session.CpuMaxRate * 100;

                // CPU rate is set for the whole system so includes all the logical CPUs. When
                // we have the CPU affinity set, we will divide the rate accordingly.
                if (session.CpuAffinityMask != 0)
                {
                    ulong affinity = systemAffinityMask & session.CpuAffinityMask;
                    uint  numberOfSelectedCores = 0;
                    for (int i = 0; i < sizeof(ulong) * 8; i++)
                    {
                        numberOfSelectedCores += (affinity & (1UL << i)) == 0 ? 0u : 1u;
                    }
                    Debug.Assert(numberOfSelectedCores < Environment.ProcessorCount);
                    finalCpuRate /= ((uint)Environment.ProcessorCount / numberOfSelectedCores);
                }

                // configure CPU rate limit
                var limitInfo = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
                {
                    ControlFlags = JOB_OBJECT_CPU_RATE_CONTROL.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE |
                                   JOB_OBJECT_CPU_RATE_CONTROL.JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP,

                    Anonymous = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION._Anonymous_e__Union {
                        CpuRate = finalCpuRate
                    }
                };
                var size = (uint)Marshal.SizeOf(limitInfo);
                CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectCpuRateControlInformation,
                                                                 &limitInfo, size));
            }
        }
        private static unsafe void SetBasicLimits(Win32Job job, SessionSettings session, ulong systemAffinityMask)
        {
            var limitInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
            var size      = (uint)Marshal.SizeOf(limitInfo);
            var length    = 0u;

            CheckWin32Result(PInvoke.QueryInformationJobObject(job.JobHandle,
                                                               JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, &limitInfo, size, &length));
            Debug.Assert(length == size);

            JOB_OBJECT_LIMIT flags = 0;

            if (!session.PropagateOnChildProcesses)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
            }

            if (session.MaxProcessMemory > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_PROCESS_MEMORY;
                limitInfo.ProcessMemoryLimit = (UIntPtr)session.MaxProcessMemory;
            }

            if (session.MaxJobMemory > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_JOB_MEMORY;
                limitInfo.JobMemoryLimit = (UIntPtr)session.MaxJobMemory;
            }

            if (session.MaxWorkingSetSize > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_WORKINGSET;
                limitInfo.BasicLimitInformation.MaximumWorkingSetSize = (UIntPtr)session.MaxWorkingSetSize;
                limitInfo.BasicLimitInformation.MinimumWorkingSetSize = (UIntPtr)session.MinWorkingSetSize;
            }

            if (session.ProcessUserTimeLimitInMilliseconds > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_PROCESS_TIME;
                limitInfo.BasicLimitInformation.PerProcessUserTimeLimit = 10_000 * session.ProcessUserTimeLimitInMilliseconds; // in 100ns
            }

            if (session.JobUserTimeLimitInMilliseconds > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_JOB_TIME;
                limitInfo.BasicLimitInformation.PerJobUserTimeLimit = 10_000 * session.JobUserTimeLimitInMilliseconds; // in 100ns
            }

            // only if NUMA node is not provided we will set the CPU affinity,
            // otherwise we will set the affinity on a selected NUMA node
            if (session.CpuAffinityMask != 0 && session.NumaNode == 0xffff)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_AFFINITY;
                // NOTE: this could result in an overflow on 32-bit apps, but I can't
                // think of a nice way of handling it here
                limitInfo.BasicLimitInformation.Affinity = new UIntPtr(systemAffinityMask & session.CpuAffinityMask);
            }

            if (flags != 0)
            {
                limitInfo.BasicLimitInformation.LimitFlags = flags | limitInfo.BasicLimitInformation.LimitFlags;
                CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle,
                                                                 JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, &limitInfo, size));
            }
        }