using Microsoft.Win32.SafeHandles;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using Vanara.Extensions;
using Vanara.InteropServices;
using Vanara.PInvoke;
using static Vanara.PInvoke.Kernel32;
#if NET20
namespace System.IO
{
/// Specifies whether the underlying handle is inheritable by child processes.
public enum HandleInheritability
{
/// Specifies that the handle is not inheritable by child processes.
None = 0,
/// Specifies that the handle is inheritable by child processes.
Inheritable = 1,
}
}
#endif
namespace Vanara.Diagnostics
{
/// The job limit type exceeded as communicated by a .
public enum JobLimit
{
/// The or value was exceeded.
IoRateControlTolerance = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL,
/// The value was exceeded.
IoReadBytes = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_READ_BYTES,
/// The value was exceeded.
IoWriteBytes = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_WRITE_BYTES,
/// The value was exceeded.
JobLowMemory = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY_LOW,
/// The value was exceeded.
JobMemory = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY,
/// The or value was exceeded.
NetRateControlTolerance = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL,
/// The value was exceeded.
PerJobUserTime = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME,
/// The or value was exceeded.
RateControlTolerance = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL,
}
///
/// Represents a system Job Object that allows groups of processes to be managed as a unit. Job objects are nameable, securable, sharable
/// objects that control attributes of the processes associated with them. Operations performed on a job object affect all processes
/// associated with the job object. Examples include enforcing limits such as working set size and process priority or terminating all
/// processes associated with a job. For more information see
/// Job Objects.
///
///
public class Job : IDisposable
{
internal SafeHJOB hJob;
private readonly IoCompletionPort complPort;
private bool disposedValue = false;
private JobLimits limits;
private JobNotifications notifications;
private JobProcessCollection processes;
private JobSettings settings;
private JobStatistics stats;
private Job(SafeHJOB jobHandle)
{
if (jobHandle is null || jobHandle.IsNull)
throw new ArgumentNullException(nameof(jobHandle));
hJob = jobHandle;
// Create completion port
complPort = IoCompletionPort.Create();
complPort.AddKeyHandler(hJob.DangerousGetHandle(), FireEventThread);
AssociateCompletionPort(complPort.Handle, hJob.DangerousGetHandle());
}
/// Finalizes an instance of the class.
~Job() => Dispose(false);
/// Action with a parameter passed by reference.
/// The parameter type.
/// The first parameter value.
internal delegate void RefAction(ref T t1);
/// Function with parameter passed by reference.
/// The type of the parameter.
/// The type of the return value.
/// The type instance on which to act.
/// The return value.
internal delegate T2 RefFunc(ref T1 t1);
///
/// Indicates that a process associated with the job exited with an exit code that indicates an abnormal exit (see the list following
/// this table).
///
public event EventHandler AbnormalProcessExit;
///
/// Indicates that the active process count has been decremented to 0. For example, if the job currently has two active processes,
/// the system sends this message after they both terminate.
///
public event EventHandler ActiveProcessCountZero;
/// Indicates that the active process limit has been exceeded.
public event EventHandler ActiveProcessLimitExceeded;
///
/// Indicates that the Settings property is set to
/// and the end-of-job time limit has been reached. Upon posting this message, the time limit is canceled and
/// the job's processes can continue to run.
///
public event EventHandler EndOfJobTime;
///
/// Indicates that a process has exceeded a per-process time limit. The system sends this message after the process termination has
/// been requested.
///
public event EventHandler EndofProcessTime;
///
/// Indicates that a process associated with the job caused the job to exceed the job-wide memory limit (if one is in effect). The
/// system does not send this message if the process has not yet reported its process identifier.
///
public event EventHandler JobMemoryLimitExceeded;
///
/// Indicates that a process associated with a job that has registered for resource limit notifications has exceeded one or more
/// limits. The system does not send this message if the process has not yet reported its process identifier.
///
public event EventHandler JobNotificationLimitExceeded;
///
/// Indicates that a process has been added to the job. Processes added to a job at the time a completion port is associated are also reported.
///
public event EventHandler NewProcess;
/// Indicates that a process associated with the job has exited.
public event EventHandler ProcessExited;
///
/// Indicates that a process associated with the job has exceeded its memory limit (if one is in effect). The system does not send
/// this message if the process has not yet reported its process identifier.
///
public event EventHandler ProcessMemoryLimitExceeded;
/// Exposes the handle (HJOB) of the job.
/// The handle.
public IntPtr Handle => hJob.DangerousGetHandle();
/// Notification limits that can be set for various properties.
/// The notifications.
public JobNotifications Notifications => notifications ?? (notifications = new JobNotifications(this));
/// Gets the processes assigned to this job.
/// The process list for the job.
public IReadOnlyCollection Processes => processes ?? (processes = new JobProcessCollection(this));
/// Gets or sets the list of processor groups to which the job is currently assigned.
public IEnumerable ProcessorGroups
{
get
{
using var mem = new SafeHGlobalHandle(4);
uint req;
while (!QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectGroupInformation, mem, mem.Size, out req))
{
Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_MORE_DATA);
mem.Size = req;
}
return mem.ToEnumerable((int)req / 2).TakeWhile(id => id > 0);
}
set
{
using var mem = SafeHGlobalHandle.CreateFromList(value);
if (!SetInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectGroupInformation, mem, mem.Size))
Win32Error.ThrowLastError();
}
}
/// Gets the hard limits for different runtime values of the job object.
/// The runtime limits.
public JobLimits RuntimeLimits => limits ?? (limits = new JobLimits(this));
/// Gets the job settings.
/// The job settings.
public JobSettings Settings => settings ?? (settings = new JobSettings(this));
/// Usage statistics for the job.
/// Usage statistics.
public JobStatistics Statistics => stats ?? (stats = new JobStatistics(this));
/// Creates or opens a job object.
///
/// The name of the job. The name is limited to MAX_PATH characters. Name comparison is case-sensitive.
/// If lpName is , the job is created without a name.
///
/// If lpName matches the name of an existing event, semaphore, mutex, waitable timer, or file-mapping object, the function fails and
/// an exception corresponding to ERROR_INVALID_HANDLE is thrown. This occurs because these objects share the same namespace.
///
/// The object can be created in a private namespace. For more information, see Object Namespaces.
///
/// Terminal Services: The name can have a "Global" or "Local" prefix to explicitly create the object in the global or session
/// namespace. The remainder of the name can contain any character except the backslash character (). For more information, see
/// Kernel Object Namespaces.
///
///
///
/// An optional instance that specifies the security descriptor for the job object and determines whether
/// child processes can inherit the returned handle. If , the job object gets a default security descriptor and
/// the handle cannot be inherited. The ACLs in the default security descriptor for a job object come from the primary or
/// impersonation token of the creator.
///
///
/// If this value is , processes created by this process will inherit the handle.
/// Otherwise, the processes do not inherit this handle.
///
///
/// If the function succeeds, the return value is a object. The handle has the JOB_OBJECT_ALL_ACCESS access right.
///
///
///
/// When a job is created, its accounting information is initialized to zero, all limits are inactive, and there are no associated
/// processes. To assign a process to a job object, use the AssignProcessToJobObject function. To get or set limits for a job, use
/// the object's properties.
///
///
/// All processes associated with a job must run in the same session. A job is associated with the session of the first process to be
/// assigned to the job.
///
/// Windows Server 2003 and Windows XP: A job is associated with the session of the process that created it.
///
/// If the job has the KillOnJobClose property set to , closing the last job object handle terminates all
/// associated processes and then destroys the job object itself.
///
///
public static Job Create(string jobName = null, JobSecurity jobSecurity = null, HandleInheritability inheritable = HandleInheritability.None)
{
var sa = GetSecAttr(jobSecurity, inheritable == HandleInheritability.Inheritable, out var hMem);
var job = new Job(CreateJobObject(sa, jobName));
hMem?.Dispose();
return job;
}
/// Performs an implicit conversion from to .
/// The job.
/// The result of the conversion.
public static implicit operator HJOB(Job job) => job.hJob;
/// Performs an implicit conversion from to .
/// The Job instance.
/// The result of the conversion.
public static implicit operator SafeWaitHandle(Job job) => new SafeWaitHandle(job.hJob.DangerousGetHandle(), false);
/// Opens an existing job object.
///
/// The name of the job to be opened. Name comparisons are case sensitive.
/// This function can open objects in a private namespace. For more information, see Object Namespaces.
///
/// Terminal Services: The name can have a "Global\" or "Local\" prefix to explicitly open the object in the global or session
/// namespace. The remainder of the name can contain any character except the backslash character (\). For more information, see
/// Kernel Object Namespaces.
///
///
///
/// The access to the job object. This parameter can be one or more of the job object access rights. This access right is checked
/// against any security descriptor for the object.
///
///
/// If this value is , processes created by this process will inherit the handle.
/// Otherwise, the processes do not inherit this handle.
///
/// If the function succeeds, the return value is a object.
public static Job Open(string jobName, JobAccessRight desiredAccess = JobAccessRight.JOB_OBJECT_ALL_ACCESS, HandleInheritability inheritable = HandleInheritability.None) =>
new Job(OpenJobObject((uint)desiredAccess, inheritable == HandleInheritability.Inheritable, jobName));
/// Assigns a process to an existing job object.
///
///
/// The process to associate with the job object. The process must have the PROCESS_SET_QUOTA and PROCESS_TERMINATE access rights.
///
///
/// If the process is already associated with a job, this job must be empty or it must be in the hierarchy of nested jobs to which
/// the process already belongs, and it cannot have UI limits set.
///
///
/// Windows 7, Windows Server 2008 R2, Windows XP with SP3, Windows Server 2008, Windows Vista and Windows Server 2003: The
/// process must not already be assigned to a job; if it is, the function fails with . This
/// behavior changed starting in Windows 8 and Windows Server 2012.
///
/// Terminal Services: All processes within a job must run within the same session as the job.
///
public void AssignProcess(Process process)
{
if (process is null) throw new ArgumentNullException(nameof(process));
CheckState();
if (!AssignProcessToJobObject(hJob, process))
Win32Error.ThrowLastError();
}
/// Associates a completion port with this job. You can associate one completion port with a job.
///
/// The completion port to use in the CompletionPort parameter of the PostQueuedCompletionStatus function when messages are sent on
/// behalf of the job.
///
/// Windows 8, Windows Server 2012, Windows 8.1, Windows Server 2012 R2, Windows 10 and Windows Server 2016: Specify
/// to remove the association between the current completion port and the job.
///
///
///
/// The value to use in the dwCompletionKey parameter of PostQueuedCompletionStatus when messages are sent on behalf of the job.
///
public void AssociateCompletionPort(HANDLE completionPort, IntPtr key = default) =>
CheckThenSet((ref JOBOBJECT_ASSOCIATE_COMPLETION_PORT i) => { i.CompletionKey = key; i.CompletionPort = completionPort; });
/// Determines whether the process is running in this job.
///
/// The process to be tested. The handle must have the PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access right.
///
/// if the job contains the specified process; otherwise, .
public bool ContainsProcess(Process process)
{
if (process is null) throw new ArgumentNullException(nameof(process));
CheckState();
if (!IsProcessInJob(process, hJob, out var isIn))
Win32Error.ThrowLastError();
return isIn;
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// Gets information about the control of the I/O rate for a job object.
///
/// The name of the volume to query. If this value is , the function gets the information about I/O rate
/// control for the job for all of the volumes for the system.
///
///
/// An array of JOBOBJECT_IO_RATE_CONTROL_INFORMATION structures that contain the information about I/O rate control for the job.
///
public JOBOBJECT_IO_RATE_CONTROL_INFORMATION[] GetIoRateControlInformation(string VolumeName = null) =>
QueryIoRateControlInformationJobObject(hJob, VolumeName);
///
/// Grants or denies access to a handle to a User object to a job that has a user-interface restriction. When access is granted, all
/// processes associated with the job can subsequently recognize and use the handle. When access is denied, the processes can no
/// longer use the handle. For more information see User Objects.
///
/// A handle to the User object.
///
/// If this parameter is , all processes associated with the job can recognize and use the handle. If the
/// parameter is , the processes cannot use the handle.
///
///
///
///
/// The GrantUserAccess function can be called only from a process not associated with the job specified by the hJob
/// parameter. The User handle must not be owned by a process or thread associated with the job.
///
/// To create user-interface restrictions, user the property.
///
public void GrantUserAccess(IUserHandle hUserObj, bool grant)
{
if (!User32.UserHandleGrantAccess(hUserObj.DangerousGetHandle(), hJob, grant))
Win32Error.ThrowLastError();
}
///
/// Starts a process resource by specifying the name of an application and a set of command-line arguments, and associates the
/// resource with a new Process component which is assigned to this job.
///
/// The name of an application file to run in the process.
/// Command-line arguments to pass when starting the process.
///
/// A new that is associated with the process resource, or if no process resource is
/// started. Note that a new process that's started alongside already running instances of the same process will be independent from
/// the others. In addition, Start may return a non-null Process with its property already set to
/// . In this case, the started process may have activated an existing instance of itself and then exited.
///
public Process StartProcess(string filename, string arguments = null) => !string.IsNullOrEmpty(filename) ? StartProcess(new ProcessStartInfo(filename, arguments)) : throw new ArgumentNullException(nameof(filename));
///
/// Starts the process resource that is specified by the parameter containing process start information (for example, the file name
/// of the process to start) and associates the resource with a new Process component which is assigned to this job.
///
///
/// The that contains the information that is used to start the process, including the file name and
/// any command-line arguments.
///
///
/// A new that is associated with the process resource, or if no process resource is
/// started. Note that a new process that's started alongside already running instances of the same process will be independent from
/// the others. In addition, Start may return a non-null Process with its property already set to
/// . In this case, the started process may have activated an existing instance of itself and then exited.
///
public Process StartProcess(ProcessStartInfo startInfo)
{
startInfo.UseShellExecute = false;
var proc = new Process { StartInfo = startInfo };
proc.StartEx(CREATE_PROCESS.CREATE_SUSPENDED);
try
{
AssignProcess(proc);
}
catch
{
proc.Kill();
throw;
}
proc.ResumePrimaryThread();
return proc;
}
///
/// Terminates all processes currently associated with the job. If the job is nested, this function terminates all processes
/// currently associated with the job and all of its child jobs in the hierarchy.
///
/// The exit code to be used by all processes and threads in the job object.
public void TerminateAllProcesses(uint exitCode = 0)
{
CheckState();
if (!TerminateJobObject(hJob, exitCode))
Win32Error.ThrowLastError();
}
internal static SECURITY_ATTRIBUTES GetSecAttr(JobSecurity sec, bool inheritable, out ISafeMemoryHandle hMem)
{
hMem = null;
if (sec is null && !inheritable) return null;
hMem = new SafeHGlobalHandle(sec.GetSecurityDescriptorBinaryForm());
return new SECURITY_ATTRIBUTES
{
bInheritHandle = inheritable,
lpSecurityDescriptor = hMem.DangerousGetHandle()
};
}
internal void CheckState()
{
if (disposedValue)
throw new InvalidOperationException("Object has been disposed.");
}
internal T CheckThenGet(JOBOBJECTINFOCLASS iClass = 0) where T : struct => CheckThenGet(n => n, iClass);
internal T CheckThenGet(Func func, JOBOBJECTINFOCLASS iClass = 0) where T2 : struct
{
CheckState();
if (iClass == 0 && !CorrespondingTypeAttribute.CanGet(out iClass))
throw new InvalidOperationException("Invalid property retrieval.");
var n = QueryInformationJobObject(hJob, iClass);
return func(n);
}
internal void CheckThenSet(RefAction action, JOBOBJECTINFOCLASS iClass = 0) where T : struct
{
CheckState();
if (iClass == 0 && !CorrespondingTypeAttribute.CanSet(out iClass))
throw new InvalidOperationException("Invalid property retrieval.");
var info = CorrespondingTypeAttribute.CanGet(iClass, typeof(T)) ? QueryInformationJobObject(hJob, iClass) : default;
action?.Invoke(ref info);
SetInformationJobObject(hJob, iClass, info);
}
/// Releases unmanaged and - optionally - managed resources.
///
/// to release both managed and unmanaged resources; to release only unmanaged resources.
///
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
limits?.Dispose();
notifications?.Dispose();
processes?.Dispose();
settings?.Dispose();
stats?.Dispose();
}
// Close the completion port handle
complPort.Dispose();
// Close the job.
hJob.Dispose();
disposedValue = true;
}
}
private void FireEventThread(uint msg, IntPtr key, IntPtr ppid)
{
if (disposedValue) return;
var t = new JobEventArgs((JOB_OBJECT_MSG)msg, ppid.ToInt32());
switch (t.JobMessage)
{
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_END_OF_JOB_TIME:
EndOfJobTime?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_END_OF_PROCESS_TIME:
EndofProcessTime?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT:
ActiveProcessLimitExceeded?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
ActiveProcessCountZero?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_NEW_PROCESS:
NewProcess?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_EXIT_PROCESS:
ProcessExited?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:
AbnormalProcessExit?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT:
ProcessMemoryLimitExceeded?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_JOB_MEMORY_LIMIT:
JobMemoryLimitExceeded?.Invoke(this, t);
break;
case JOB_OBJECT_MSG.JOB_OBJECT_MSG_NOTIFICATION_LIMIT:
var vi = GetViolation();
Debug.WriteLine($"Notification: {vi.ViolationLimitFlags}");
foreach (var l in vi.ViolationLimitFlags.GetFlags().Cast())
{
object v = null, n = null;
switch (l)
{
case JobLimit.JobMemory:
v = vi.JobMemory;
n = vi.JobMemoryLimit;
break;
case JobLimit.PerJobUserTime:
v = vi.PerJobUserTime;
n = vi.PerJobUserTimeLimit;
break;
case JobLimit.IoReadBytes:
v = vi.IoReadBytes;
n = vi.IoReadBytesLimit;
break;
case JobLimit.IoWriteBytes:
v = vi.IoWriteBytes;
n = vi.IoWriteBytesLimit;
break;
case JobLimit.RateControlTolerance:
v = vi.RateControlTolerance;
n = vi.RateControlToleranceLimit;
break;
case JobLimit.IoRateControlTolerance:
v = vi.IoRateControlTolerance;
n = vi.IoRateControlToleranceLimit;
break;
case JobLimit.JobLowMemory:
v = vi.JobMemory;
n = vi.JobLowMemoryLimit;
break;
case JobLimit.NetRateControlTolerance:
v = vi.NetRateControlTolerance;
n = vi.NetRateControlToleranceLimit;
break;
default:
Debug.WriteLine($"Unable to process notification: {vi.ViolationLimitFlags}, {vi.LimitFlags}");
continue;
}
JobNotificationLimitExceeded?.Invoke(this, new JobNotificationEventArgs(t.JobMessage, t.ProcessId, l, v, n));
}
break;
default:
break;
}
JOBOBJECT_LIMIT_VIOLATION_INFORMATION_2 GetViolation()
{
using var mem = SafeHeapBlock.CreateFromStructure();
if (!QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectLimitViolationInformation2, mem, mem.Size, out _))
{
Debug.WriteLine($"Failed to get JOBOBJECT_LIMIT_VIOLATION_INFORMATION_2: {Win32Error.GetLastError()}");
if (!QueryInformationJobObject(hJob, JOBOBJECTINFOCLASS.JobObjectLimitViolationInformation, mem, (uint)Marshal.SizeOf(typeof(JOBOBJECT_LIMIT_VIOLATION_INFORMATION)), out _))
Debug.WriteLine($"Failed to get JOBOBJECT_LIMIT_VIOLATION_INFORMATION: {Win32Error.GetLastError()}");
}
return mem.ToStructure();
}
}
internal class JobProcessCollection : JobHelper, IReadOnlyCollection
{
public JobProcessCollection(Job j) : base(j)
{
}
///
/// The total number of processes currently associated with the job. When a process is associated with a job, but the association
/// fails because of a limit violation, this value is temporarily incremented. When the terminated process exits and all
/// references to the process are released, this value is decremented.
///
public int Count => (int)job.CheckThenGet().ActiveProcesses;
public IEnumerator GetEnumerator() => new Enumerator(job);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private class Enumerator : IEnumerator
{
private int i;
private Job job;
private Process[] procs;
public Enumerator(Job j)
{
job = j;
Reset();
}
public Process Current => procs[i];
object IEnumerator.Current => Current;
public void Dispose() => job = null;
public bool MoveNext() => ++i < procs.Length;
public void Reset()
{
i = -1;
using var mem = SafeHGlobalHandle.CreateFromStructure();
while (!Kernel32.QueryInformationJobObject(job, JOBOBJECTINFOCLASS.JobObjectBasicProcessIdList, mem.DangerousGetHandle(), mem.Size, out _))
{
Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_MORE_DATA);
mem.Size *= 2;
}
var l = mem.ToStructure();
procs = mem.ToEnumerable((int)l, 8).Select(p => { try { return Process.GetProcessById((int)p.ToUInt32()); } catch { return null; } }).Where(p => p != null).ToArray();
}
}
}
}
/// Contains information about a job object message.
///
public class JobEventArgs : EventArgs
{
internal JobEventArgs(JOB_OBJECT_MSG msg, int id = 0)
{
JobMessage = msg;
ProcessId = id;
}
/// Gets the type of job message posted.
/// The job message.
public JOB_OBJECT_MSG JobMessage { get; }
/// Gets the process identifier of the process referred to by the message.
/// The process identifier. This value can be 0.
public int ProcessId { get; }
}
/// Base class for other classes that support the object.
///
public abstract class JobHelper : IDisposable
{
/// The job object.
protected Job job;
/// Initializes a new instance of the class.
/// Name of the job.
protected JobHelper(string jobName) : this(Job.Open(jobName, JobAccessRight.JOB_OBJECT_QUERY | JobAccessRight.JOB_OBJECT_SET_ATTRIBUTES)) { }
/// Initializes a new instance of the class.
/// The job.
protected JobHelper(Job job) => this.job = job;
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public virtual void Dispose() => job = null;
/// Gets field values from JOBOBJECT_BASIC_LIMIT_INFORMATION.
/// The return type.
/// The limit flag.
/// The method to get the field.
/// The value.
internal T? GetBasic(JOBOBJECT_LIMIT_FLAGS flag, Func getter) where T : struct =>
job.CheckThenGet(n => n.LimitFlags.IsFlagSet(flag) ? (T?)getter(n) : null);
/// Sets a field value in JOBOBJECT_BASIC_LIMIT_INFORMATION.
/// The field type.
/// The limit flag.
/// The value.
/// The method to set the field.
internal void SetBasic(JOBOBJECT_LIMIT_FLAGS flag, T? value, Job.RefAction setter) where T : struct =>
job.CheckThenSet((ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => { i.LimitFlags = (JOBOBJECT_LIMIT_FLAGS)0xFF & i.LimitFlags.SetFlags(flag, value.HasValue); setter(ref i); });
}
/// Settings for that set limits for different runtime values.
///
public class JobLimits : JobHelper
{
/// Initializes a new instance of the class.
/// Name of the job.
public JobLimits(string jobName) : base(jobName) { }
/// Initializes a new instance of the class.
/// The job.
public JobLimits(Job job) : base(job) { }
///
/// Gets or sets the active process limit for the job.
///
/// If you try to associate a process with a job, and this causes the active process count to exceed this limit, the process is
/// terminated and the association fails.
///
///
public uint? ActiveProcessLimit
{
get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_ACTIVE_PROCESS, n => n.ActiveProcessLimit);
set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_ACTIVE_PROCESS, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.ActiveProcessLimit = value.GetValueOrDefault());
}
///
/// Gets or sets the job's CPU rate as a hard limit. If set, after the job reaches its CPU cycle limit for the current scheduling
/// interval, no threads associated with the job will run until the next interval.
///
///
/// Specifies the portion of processor cycles that the threads in a job object can use during each scheduling interval, as a
/// percentage of cycles. This value is greater than 0.0 and less than equal to 100.0. If this value is , then
/// this setting is disabled.
///
/// Value must be greater than 0.0 and less than equal to 100.0.
public double? CpuRateLimit
{
get => job.CheckThenGet((JOBOBJECT_CPU_RATE_CONTROL_INFORMATION n) =>
n.ControlFlags.IsFlagSet(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE)
? n.Union.CpuRate / 100.0 : (double?)null);
set
{
if (value.HasValue && (value.Value <= 0.0 || value.Value > 100.0))
throw new ArgumentOutOfRangeException(nameof(CpuRateLimit));
job.CheckThenSet((ref JOBOBJECT_CPU_RATE_CONTROL_INFORMATION i) =>
{
i.ControlFlags = i.ControlFlags.SetFlags(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE, value.HasValue);
i.Union.CpuRate = value.HasValue ? (uint)(value.Value * 100) : 0;
});
}
}
/// Gets or sets the CPU rate for the job as limited by minimum and maximum rates.
///
///
/// Specifies the minimum and maximum portions of the processor cycles that the threads in a job object can reserve during each
/// scheduling interval. Specify these rates as a percentage from 0.0 to 100.0.
///
///
/// For the minimum rates to work correctly, the sum of the minimum rates for all of the job objects in the system cannot exceed
/// 100%. After the job reaches the maximum limit for a scheduling interval, no threads associated with the job can run until the
/// next scheduling interval.
///
/// If this value is , then this setting is disabled.
///
///
/// Values must be greater than or equal to 0.0 and less than equal to 100.0 and the minimum value must be less than the maximum value.
///
public (double minPortion, double maxPortion)? CpuRatePortion
{
get => job.CheckThenGet((JOBOBJECT_CPU_RATE_CONTROL_INFORMATION n) =>
n.ControlFlags.IsFlagSet(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_MIN_MAX_RATE | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE)
? (n.Union.MinRate / 100.0, n.Union.MaxRate / 100.0) : ((double, double)?)null);
set
{
if (value.HasValue && (value.Value.minPortion < 0.0 || value.Value.minPortion > 100.0 || value.Value.maxPortion < 0.0 || value.Value.maxPortion > 100.0 || value.Value.minPortion > value.Value.maxPortion))
throw new ArgumentOutOfRangeException(nameof(CpuRatePortion));
job.CheckThenSet((ref JOBOBJECT_CPU_RATE_CONTROL_INFORMATION i) =>
{
i.ControlFlags = i.ControlFlags.SetFlags(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_MIN_MAX_RATE | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE, value.HasValue);
i.Union.MinRate = value.HasValue ? (ushort)(value.Value.minPortion * 100) : (ushort)0;
i.Union.MaxRate = value.HasValue ? (ushort)(value.Value.maxPortion * 100) : (ushort)0;
});
}
}
/// Gets or sets the job's CPU rate when calculated based on its relative weight to the weight of other jobs.
///
///
/// Specifies the scheduling weight of the job object, which determines the share of processor time given to the job relative to
/// other workloads on the processor.
///
///
/// This member can be a value from 1 through 9, where 1 is the smallest share and 9 is the largest share. The default is 5, which
/// should be used for most workloads.
///
/// If this value is , then this setting is disabled.
///
/// weight1to9
public int? CpuRateRelativeWeight
{
get => job.CheckThenGet((JOBOBJECT_CPU_RATE_CONTROL_INFORMATION n) =>
n.ControlFlags.IsFlagSet(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE)
? (int)n.Union.Weight : (int?)null);
set
{
if (value.HasValue && (value.Value < 1 || value.Value > 9))
throw new ArgumentOutOfRangeException(nameof(CpuRateRelativeWeight));
job.CheckThenSet((ref JOBOBJECT_CPU_RATE_CONTROL_INFORMATION i) =>
{
i.ControlFlags = i.ControlFlags.SetFlags(JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED | JOB_OBJECT_CPU_RATE_CONTROL_FLAGS.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE, value.HasValue);
i.Union.Weight = value.HasValue ? (uint)value.Value : 0;
});
}
}
///
/// Gets or sets the limit for the virtual memory that can be committed for the job.
/// If this value is , then this setting is disabled.
///
public ulong? JobMemoryLimit
{
get => job.CheckThenGet((JOBOBJECT_EXTENDED_LIMIT_INFORMATION n) =>
n.BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY) ? (ulong?)n.JobMemoryLimit : null);
set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY, value.HasValue); i.JobMemoryLimit = value.GetValueOrDefault(); });
}
///
/// Gets or sets the maximum bandwidth for outgoing network traffic for the job, in bytes.
/// If this value is , then this setting is disabled.
///
public ulong? MaxBandwidth
{
get
{
var info = job.CheckThenGet();
return info.ControlFlags.IsFlagSet(JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_MAX_BANDWIDTH) ? (ulong?)info.MaxBandwidth : null;
}
set
{
job.CheckThenSet((ref JOBOBJECT_NET_RATE_CONTROL_INFORMATION i) =>
{
i.ControlFlags = i.ControlFlags.SetFlags(JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_MAX_BANDWIDTH | JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE, value.HasValue);
i.MaxBandwidth = value.GetValueOrDefault();
});
}
}
///
/// Gets or sets the per-job user-mode execution time limit.
///
/// The system adds the current time of the processes associated with the job to this limit. For example, if you set this limit to 1
/// minute, and the job has a process that has accumulated 5 minutes of user-mode time, the limit actually enforced is 6 minutes.
///
///
/// The system periodically checks to determine whether the sum of the user-mode execution time for all processes is greater than
/// this end-of-job limit. If it is, the action specified in the EndOfJobTimeAction property is carried out. By default, all
/// processes are terminated and the status code is set to ERROR_NOT_ENOUGH_QUOTA.
///
/// If this value is , then this setting is disabled.
///
public TimeSpan? PerJobUserTimeLimit
{
get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, n => n.PerJobUserTimeLimit);
set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.PerJobUserTimeLimit = value.GetValueOrDefault());
}
///
/// Gets or sets the per-process user-mode execution time limit.
///
/// The system periodically checks to determine whether each process associated with the job has accumulated more user-mode time than
/// the set limit. If it has, the process is terminated.
///
/// If the job is nested, the effective limit is the most restrictive limit in the job chain.
/// If this value is , then this setting is disabled.
///
public TimeSpan? PerProcessUserTimeLimit
{
get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PROCESS_TIME, n => n.PerProcessUserTimeLimit);
set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PROCESS_TIME, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.PerProcessUserTimeLimit = value.GetValueOrDefault());
}
///
/// Gets or sets the limit for the virtual memory that can be committed by a process.
/// If this value is , then this setting is disabled.
///
public ulong? ProcessMemoryLimit
{
get => job.CheckThenGet((JOBOBJECT_EXTENDED_LIMIT_INFORMATION n) =>
n.BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PROCESS_MEMORY) ? (ulong?)n.ProcessMemoryLimit : null);
set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PROCESS_MEMORY, value.HasValue); i.ProcessMemoryLimit = value.GetValueOrDefault(); });
}
///
/// Gets or sets the working set size in bytes for each process associated with the job.
/// Both min and max must be zero or non-zero.
/// If this value is , then this setting is disabled.
///
public (SizeT min, SizeT max)? WorkingSetSize
{
get => job.CheckThenGet((JOBOBJECT_BASIC_LIMIT_INFORMATION n) =>
n.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_WORKINGSET) ? (n.MinimumWorkingSetSize, n.MaximumWorkingSetSize) : ((SizeT, SizeT)?)null);
set
{
if (value.HasValue && ((value.Value.min == SizeT.Zero && value.Value.max != SizeT.Zero) || (value.Value.max == SizeT.Zero && value.Value.min != SizeT.Zero) || value.Value.min > value.Value.max))
throw new ArgumentOutOfRangeException(nameof(WorkingSetSize));
job.CheckThenSet((ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) =>
{
i.LimitFlags = i.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_WORKINGSET, value.HasValue);
i.MinimumWorkingSetSize = value.HasValue ? value.Value.min : SizeT.Zero;
i.MaximumWorkingSetSize = value.HasValue ? value.Value.max : SizeT.Zero;
});
}
}
}
/// Contains information about a job object limit notification message.
///
public class JobNotificationEventArgs : JobEventArgs
{
internal JobNotificationEventArgs(JOB_OBJECT_MSG msg, int id, JobLimit k, object v, object n) : base(msg, id)
{
Limit = k;
ReportedValue = v;
NotificationLimit = n;
}
/// Gets the limit which was exceeded.
/// The limit.
public JobLimit Limit { get; }
/// Gets the value of the notification limit.
/// The notification limit value.
public object NotificationLimit { get; }
/// Gets the value of the limited item at the time of the notification.
/// The reported value at the time of notification.
public object ReportedValue { get; }
}
/// Settings for that set notification limits for different properties.
///
public class JobNotifications : JobHelper
{
/// Initializes a new instance of the class.
/// Name of the job.
public JobNotifications(string jobName) : base(jobName) { }
/// Initializes a new instance of the class.
/// The job.
public JobNotifications(Job job) : base(job) { }
///
///
/// Gets or sets the extent to which a job can exceed its I/O rate control limits during the interval specified by the
/// IoRateControlToleranceInterval member.
///
/// If this value is , then this setting is disabled.
/// This member can be one of the following values. If no value is specified, ToleranceHigh is used.
///
///
///
/// Value
/// Meaning
///
/// -
/// ToleranceLow
/// The job can exceed its I/O rate control limits for 20% of the tolerance interval.
///
/// -
/// ToleranceMedium
/// The job can exceed its I/O rate control limits for 40% of the tolerance interval.
///
/// -
/// ToleranceHigh
/// The job can exceed its I/O rate control limits for 60% of the tolerance interval.
///
///
///
///
public JOBOBJECT_RATE_CONTROL_TOLERANCE? IoRateControlTolerance
{
get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, i => i.IoRateControlTolerance);
set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) =>
{
i.IoRateControlTolerance = value.GetValueOrDefault();
if (value.HasValue && i.IoRateControlToleranceInterval == 0)
i.IoRateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort;
});
}
///
///
/// Gets or sets the interval during which a job's I/O usage is monitored to determine whether the job has exceeded its I/O rate
/// control limits.
///
/// If this value is , then this setting is disabled.
/// This member can be one of the following values. If no value is specified, ToleranceIntervalShort is used.
///
///
///
/// Value
/// Meaning
///
/// -
/// ToleranceIntervalShort
/// The tolerance interval is 10 seconds.
///
/// -
/// ToleranceIntervalMedium
/// The tolerance interval is one minute.
///
/// -
/// ToleranceIntervalLong
/// The tolerance interval is 10 minutes.
///
///
///
///
public JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL? IoRateControlToleranceInterval
{
get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, i => i.IoRateControlToleranceInterval);
set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_IO_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) =>
{
i.IoRateControlToleranceInterval = value.GetValueOrDefault();
if (value.HasValue && i.IoRateControlTolerance == 0)
i.IoRateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceHigh;
});
}
///
/// Gets or sets the notification limit for total I/O bytes read by all processes in the job.
/// If this value is , then this setting is disabled.
///
public ulong? IoReadBytesLimit
{
get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_READ_BYTES, i => i.IoReadBytesLimit);
set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_READ_BYTES, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => i.IoReadBytesLimit = value.GetValueOrDefault());
}
///
/// Gets or sets the notification limit for total I/O bytes written by all processes in the job.
/// If this value is , then this setting is disabled.
///
public ulong? IoWriteBytesLimit
{
get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_WRITE_BYTES, i => i.IoWriteBytesLimit);
set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_WRITE_BYTES, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => i.IoWriteBytesLimit = value.GetValueOrDefault());
}
///
/// The notification limit minimum for the total virtual memory that can be committed by all processes in the job, in bytes. The
/// minimum value is 4096.
/// If this value is , then this setting is disabled.
///
public ulong? JobLowMemoryLimit
{
get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY_LOW, i => i.JobLowMemoryLimit);
set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY_LOW, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) => i.JobLowMemoryLimit = value.GetValueOrDefault());
}
///
/// The notification limit for total virtual memory that can be committed by all processes in the job, in bytes. The minimum value is 4096.
/// If this value is , then this setting is disabled.
///
public ulong? JobMemoryLimit
{
get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY, i => i.JobMemoryLimit);
set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => i.JobMemoryLimit = value.GetValueOrDefault());
}
///
///
/// Gets or sets the extent to which a job can exceed its network rate control limits during the interval specified by the
/// NetRateControlToleranceInterval member.
///
/// If this value is , then this setting is disabled.
/// This member can be one of the following values. If no value is specified, ToleranceHigh is used.
///
///
///
/// Value
/// Meaning
///
/// -
/// ToleranceLow
/// The job can exceed its network rate control limits for 20% of the tolerance interval.
///
/// -
/// ToleranceMedium
/// The job can exceed its network rate control limits for 40% of the tolerance interval.
///
/// -
/// ToleranceHigh
/// The job can exceed its network rate control limits for 60% of the tolerance interval.
///
///
///
///
public JOBOBJECT_RATE_CONTROL_TOLERANCE? NetRateControlTolerance
{
get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, i => i.NetRateControlTolerance);
set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) =>
{
i.NetRateControlTolerance = value.GetValueOrDefault();
if (value.HasValue && i.NetRateControlToleranceInterval == 0)
i.NetRateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort;
});
}
///
///
/// Gets or sets the interval during which a job's network usage is monitored to determine whether the job has exceeded its network
/// rate control limits.
///
/// If this value is , then this setting is disabled.
/// This member can be one of the following values. If no value is specified, ToleranceIntervalShort is used.
///
///
///
/// Value
/// Meaning
///
/// -
/// ToleranceIntervalShort
/// The tolerance interval is 10 seconds.
///
/// -
/// ToleranceIntervalMedium
/// The tolerance interval is one minute.
///
/// -
/// ToleranceIntervalLong
/// The tolerance interval is 10 minutes.
///
///
///
///
public JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL? NetRateControlToleranceInterval
{
get => Get2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, i => i.NetRateControlToleranceInterval);
set => Set2(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_NET_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) =>
{
i.NetRateControlToleranceInterval = value.GetValueOrDefault();
if (value.HasValue && i.NetRateControlTolerance == 0)
i.NetRateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceHigh;
});
}
///
/// Gets or sets the notification limit for per-job user-mode execution time, in 100-nanosecond ticks.
/// If this value is , then this setting is disabled.
///
/// The system adds the accumulated execution time of processes associated with the job to this limit when the limit is set. For
/// example, if a process associated with the job has already accumulated 5 minutes of user-mode execution time and the limit is set
/// to 1 minute, the limit actually enforced is 6 minutes.
///
///
/// To specify PerJobUserTimeLimit as an enforceable limit and terminate processes in jobs that exceed the limit, see the
/// JOBOBJECT_BASIC_LIMIT_INFORMATION structure.
///
///
public TimeSpan? PerJobUserTimeLimit
{
get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, i => i.PerJobUserTimeLimit);
set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_TIME, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => i.PerJobUserTimeLimit = value.GetValueOrDefault());
}
///
///
/// Gets or sets the extent to which a job can exceed its CPU rate control limits during the interval specified by the
/// RateControlToleranceInterval member.
///
/// If this value is , then this setting is disabled.
/// This member can be one of the following values. If no value is specified, ToleranceHigh is used.
///
///
///
/// Value
/// Meaning
///
/// -
/// ToleranceLow
/// The job can exceed its CPU rate control limits for 20% of the tolerance interval.
///
/// -
/// ToleranceMedium
/// The job can exceed its CPU rate control limits for 40% of the tolerance interval.
///
/// -
/// ToleranceHigh
/// The job can exceed its CPU rate control limits for 60% of the tolerance interval.
///
///
///
///
public JOBOBJECT_RATE_CONTROL_TOLERANCE? RateControlTolerance
{
get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, i => i.RateControlTolerance);
set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) =>
{
i.RateControlTolerance = value.GetValueOrDefault();
if (value.HasValue && i.RateControlToleranceInterval == 0)
i.RateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort;
});
}
///
///
/// Gets or sets the interval during which a job's CPU usage is monitored to determine whether the job has exceeded its CPU rate
/// control limits.
///
/// If this value is , then this setting is disabled.
/// This member can be one of the following values. If no value is specified, ToleranceIntervalShort is used.
///
///
///
/// Value
/// Meaning
///
/// -
/// ToleranceIntervalShort
/// The tolerance interval is 10 seconds.
///
/// -
/// ToleranceIntervalMedium
/// The tolerance interval is one minute.
///
/// -
/// ToleranceIntervalLong
/// The tolerance interval is 10 minutes.
///
///
///
///
public JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL? RateControlToleranceInterval
{
get => Get1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, i => i.RateControlToleranceInterval);
set => Set1(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_RATE_CONTROL, value, (ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) =>
{
i.RateControlToleranceInterval = value.GetValueOrDefault();
if (value.HasValue && i.RateControlTolerance == 0)
i.RateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceHigh;
});
}
/// Gets field values from JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION.
/// The return type.
/// The limit flag.
/// The method to get the field.
/// The value.
private T? Get1(JOBOBJECT_LIMIT_FLAGS flag, Func getter) where T : struct =>
job.CheckThenGet(n => n.LimitFlags.IsFlagSet(flag) ? (T?)getter(n) : null);
/// Gets field values from JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2.
/// The return type.
/// The limit flag.
/// The method to get the field.
/// The value.
private T? Get2(JOBOBJECT_LIMIT_FLAGS flag, Func getter) where T : struct =>
job.CheckThenGet(n => n.LimitFlags.IsFlagSet(flag) ? (T?)getter(n) : null);
/// Sets a field value in JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION.
/// The field type.
/// The limit flag.
/// The value.
/// The method to set the field.
private void Set1(JOBOBJECT_LIMIT_FLAGS flag, T? value, Job.RefAction setter) where T : struct =>
job.CheckThenSet((ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION i) => { i.LimitFlags = i.LimitFlags.SetFlags(flag, value.HasValue); setter(ref i); });
/// Sets a field value in JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2.
/// The field type.
/// The limit flag.
/// The value.
/// The method to set the field.
private void Set2(JOBOBJECT_LIMIT_FLAGS flag, T? value, Job.RefAction setter) where T : struct =>
job.CheckThenSet((ref JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 i) => { i.LimitFlags = i.LimitFlags.SetFlags(flag, value.HasValue); setter(ref i); });
}
/// Represents the security access rights of a job object.
public class JobSecurity : ObjectSecurity
{
///
public JobSecurity() : base(true, System.Security.AccessControl.ResourceType.KernelObject) { }
}
/// Settings related to job objects.
public class JobSettings : JobHelper
{
/// Initializes a new instance of the class.
/// Name of the job.
public JobSettings(string jobName) : base(jobName) { }
/// Initializes a new instance of the class.
/// The job.
public JobSettings(Job job) : base(job) { }
///
/// Gets or sets the processor affinity for all processes associated with the job.
///
/// The affinity must be a subset of the system affinity mask obtained by calling the GetProcessAffinityMask function. The
/// affinity of each thread is set to this value, but threads are free to subsequently set their affinity, as long as it is a subset
/// of the specified affinity mask. Processes cannot set their own affinity mask.
///
/// If this value is , then this setting is disabled.
///
public UIntPtr? Affinity
{
get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_AFFINITY, n => n.Affinity);
set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_AFFINITY, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.Affinity = value.GetValueOrDefault());
}
///
/// If any process associated with the job creates a child process using the CREATE_BREAKAWAY_FROM_JOB flag while this value is
/// , the child process is not associated with the job.
///
public bool ChildProcessBreakawayAllowed
{
get => job.CheckThenGet().BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_BREAKAWAY_OK);
set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_BREAKAWAY_OK, value); });
}
///
/// If , allows any process associated with the job to create child processes that are not associated with the
/// job. If the job is nested and its immediate job object allows breakaway, the child process breaks away from the immediate job
/// object and from each job in the parent job chain, moving up the hierarchy until it reaches a job that does not permit breakaway.
/// If the immediate job object does not allow breakaway, the child process does not break away even if jobs in its parent job chain
/// allow it.
///
public bool ChildProcessSilentBreakawayAllowed
{
get => job.CheckThenGet().BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK);
set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, value); });
}
///
/// If , Forces a call to the SetErrorMode function with the SEM_NOGPFAULTERRORBOX flag for each process
/// associated with the job. If an exception occurs and the system calls the UnhandledExceptionFilter function, the debugger will be
/// given a chance to act. If there is no debugger, the functions returns EXCEPTION_EXECUTE_HANDLER. Normally, this will cause
/// termination of the process with the exception code as the exit status.
///
public bool DieOnUnhandledException
{
get => job.CheckThenGet().BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION);
set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION, value); });
}
///
/// The value to use for the Differentiated Service code point (DSCP) field to turn on network quality of service (QoS) for all
/// outgoing network traffic generated by the processes of the job object. The valid range is from 0x00 through 0x3F. For information
/// about DSCP, see Differentiated Services.
/// If this value is , then this setting is disabled.
///
public byte? DscpTag
{
get
{
var info = job.CheckThenGet();
return info.ControlFlags.IsFlagSet(JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_DSCP_TAG | JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE) ? (byte?)info.DscpTag : null;
}
set
{
if (value > 0x3F)
throw new ArgumentOutOfRangeException(nameof(DscpTag));
var flag = JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_DSCP_TAG;
if (value.HasValue) flag |= JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE;
job.CheckThenSet((ref JOBOBJECT_NET_RATE_CONTROL_INFORMATION i) => { i.ControlFlags = flag; i.DscpTag = value.GetValueOrDefault(); });
}
}
/// Gets or sets the list of processor groups to which the job is currently assigned.
public IEnumerable GroupAffinity
{
get
{
var gaSz = Marshal.SizeOf(typeof(GROUP_AFFINITY));
using var mem = new SafeHGlobalHandle(gaSz);
uint req;
while (!QueryInformationJobObject(job.hJob, JOBOBJECTINFOCLASS.JobObjectGroupInformationEx, mem, mem.Size, out req))
{
Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_MORE_DATA);
mem.Size = req;
}
return mem.ToArray((int)req / gaSz);
}
set
{
using var mem = SafeHGlobalHandle.CreateFromList(value);
if (!SetInformationJobObject(job.hJob, JOBOBJECTINFOCLASS.JobObjectGroupInformationEx, mem, mem.Size))
Win32Error.ThrowLastError();
}
}
/// Causes all processes associated with the job to terminate when the last handle to the job is closed.
/// to kill all processes when the job is closed; otherwise, .
public bool KillOnJobClose
{
get => job.CheckThenGet().BasicLimitInformation.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE);
set => job.CheckThenSet((ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION i) => { i.BasicLimitInformation.LimitFlags = i.BasicLimitInformation.LimitFlags.SetFlags(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, value); });
}
///
/// Gets or sets the priority class for all processes associated with the job.
///
/// Processes and threads cannot modify their priority class. The calling process must enable the SE_INC_BASE_PRIORITY_NAME privilege.
///
/// If this value is , then this setting is disabled.
///
public ProcessPriorityClass? PriorityClass
{
get => (ProcessPriorityClass?)GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PRIORITY_CLASS, n => n.PriorityClass);
set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_PRIORITY_CLASS, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.PriorityClass = (uint)value.GetValueOrDefault());
}
///
/// Gets or sets the scheduling class for all processes associated with the job.
///
/// The valid values are 0 to 9. Use 0 for the least favorable scheduling class relative to other threads, and 9 for the most
/// favorable scheduling class relative to other threads. By default, this value is 5. To use a scheduling class greater than 5, the
/// calling process must enable the SE_INC_BASE_PRIORITY_NAME privilege.
///
/// If this value is , then this setting is disabled.
///
public uint? SchedulingClass
{
get => GetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_SCHEDULING_CLASS, n => n.SchedulingClass);
set => SetBasic(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_SCHEDULING_CLASS, value, (ref JOBOBJECT_BASIC_LIMIT_INFORMATION i) => i.SchedulingClass = value.GetValueOrDefault());
}
///
/// Determines if all processes will be terminated when the end-of-job time limit has been exceeded or if only an event will be sent.
/// The default is to terminate all processes.
///
/// If , the system terminates all processes and sets the exit status to ERROR_NOT_ENOUGH_QUOTA. The processes
/// cannot prevent or delay their own termination. The job object is set to the signaled state and remains signaled until this limit
/// is reset. No additional processes can be assigned to the job until the limit is reset. This is the default termination action.
///
///
/// If , the system generates the event. After the completion packet is posted,
/// the system clears the end-of-job time limit, and processes in the job can continue their execution. If no completion port is
/// associated with the job when the time limit has been exceeded, the action taken is the same as for JOB_OBJECT_TERMINATE_AT_END_OF_JOB.
///
///
public bool TerminateProcessesAtEndOfJobTimeLimit
{
get => job.CheckThenGet().EndOfJobTimeAction == JOBOBJECT_END_OF_JOB_TIME_ACTION.JOB_OBJECT_TERMINATE_AT_END_OF_JOB;
set => job.CheckThenSet((ref JOBOBJECT_END_OF_JOB_TIME_INFORMATION i) => i.EndOfJobTimeAction = (JOBOBJECT_END_OF_JOB_TIME_ACTION)(value ? 0 : 1));
}
///
/// The restriction class for the user interface. This member can be one or more of the following values.
///
///
///
/// Value
/// Meaning
///
/// -
/// JOB_OBJECT_UILIMIT_DESKTOP
///
/// Prevents processes associated with the job from creating desktops and switching desktops using the CreateDesktop and
/// SwitchDesktop functions.
///
///
/// -
/// JOB_OBJECT_UILIMIT_DISPLAYSETTINGS
/// Prevents processes associated with the job from calling the ChangeDisplaySettings function.
///
/// -
/// JOB_OBJECT_UILIMIT_EXITWINDOWS
/// Prevents processes associated with the job from calling the ExitWindows or ExitWindowsEx function.
///
/// -
/// JOB_OBJECT_UILIMIT_GLOBALATOMS
///
/// Prevents processes associated with the job from accessing global atoms. When this flag is used, each job has its own atom table.
///
///
/// -
/// JOB_OBJECT_UILIMIT_HANDLES
/// Prevents processes associated with the job from using USER handles owned by processes not associated with the same job.
///
/// -
/// JOB_OBJECT_UILIMIT_READCLIPBOARD
/// Prevents processes associated with the job from reading data from the clipboard.
///
/// -
/// JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS
/// Prevents processes associated with the job from changing system parameters by using the SystemParametersInfo function.
///
/// -
/// JOB_OBJECT_UILIMIT_WRITECLIPBOARD
/// Prevents processes associated with the job from writing data to the clipboard.
///
///
///
///
public JOBOBJECT_UILIMIT_FLAGS UIRestrictionsClass
{
get => job.CheckThenGet().UIRestrictionsClass;
set => job.CheckThenSet((ref JOBOBJECT_BASIC_UI_RESTRICTIONS i) => i.UIRestrictionsClass = value);
}
}
/// Gets statistics for a job object.
///
public class JobStatistics : JobHelper
{
/// Initializes a new instance of the class with a job name.
/// Name of the job.
public JobStatistics(string jobName) : base(jobName) { }
/// Initializes a new instance of the class with a job instance.
/// The job.
public JobStatistics(Job job) : base(job) { }
/// The number of I/O operations performed, other than read and write operations.
public ulong OtherOperationCount => job.CheckThenGet().IoInfo.OtherOperationCount;
/// The number of bytes transferred during operations other than read and write operations.
public ulong OtherTransferCount => job.CheckThenGet().IoInfo.OtherTransferCount;
/// The peak memory usage of all processes currently associated with the job.
public ulong PeakJobMemoryUsed => job.CheckThenGet().PeakJobMemoryUsed;
/// The peak memory used by any process ever associated with the job.
public ulong PeakProcessMemoryUsed => job.CheckThenGet().PeakProcessMemoryUsed;
/// The number of read operations performed.
public ulong ReadOperationCount => job.CheckThenGet().IoInfo.ReadOperationCount;
/// The number of bytes read.
public ulong ReadTransferCount => job.CheckThenGet().IoInfo.ReadTransferCount;
///
///
/// The total amount of kernel-mode execution time for all active processes associated with the job (as well as all terminated
/// processes no longer associated with the job) since the last call that set a per-job kernel-mode time limit, in 100-nanosecond ticks.
///
/// This member is set to zero on creation of the job, and each time a per-job kernel-mode time limit is established.
///
public TimeSpan ThisPeriodTotalKernelTime => job.CheckThenGet().ThisPeriodTotalKernelTime;
///
///
/// The total amount of user-mode execution time for all active processes associated with the job (as well as all terminated
/// processes no longer associated with the job) since the last call that set a per-job user-mode time limit, in 100-nanosecond ticks.
///
/// This member is set to 0 on creation of the job, and each time a per-job user-mode time limit is established.
///
public TimeSpan ThisPeriodTotalUserTime => job.CheckThenGet().ThisPeriodTotalUserTime;
///
/// The total amount of kernel-mode execution time for all active processes associated with the job, as well as all terminated
/// processes no longer associated with the job, in 100-nanosecond ticks.
///
public TimeSpan TotalKernelTime => job.CheckThenGet().TotalKernelTime;
///
/// The total number of page faults encountered by all active processes associated with the job, as well as all terminated processes
/// no longer associated with the job.
///
public uint TotalPageFaultCount => job.CheckThenGet().TotalPageFaultCount;
///
/// The total number of processes associated with the job during its lifetime, including those that have terminated. For example,
/// when a process is associated with a job, but the association fails because of a limit violation, this value is incremented.
///
public uint TotalProcesses => job.CheckThenGet().TotalProcesses;
/// The total number of processes terminated because of a limit violation.
public uint TotalTerminatedProcesses => job.CheckThenGet().TotalTerminatedProcesses;
///
/// The total amount of user-mode execution time for all active processes associated with the job, as well as all terminated
/// processes no longer associated with the job, in 100-nanosecond ticks.
///
public TimeSpan TotalUserTime => job.CheckThenGet().TotalUserTime;
/// The number of write operations performed.
public ulong WriteOperationCount => job.CheckThenGet().IoInfo.WriteOperationCount;
/// The number of bytes written.
public ulong WriteTransferCount => job.CheckThenGet().IoInfo.WriteTransferCount;
}
}