mirror of https://github.com/dahall/Vanara.git
Added .NET friendly wrappers for Job Objects and I/O Completion Ports with tests.
parent
8812c2cec3
commit
c73424ae99
|
@ -0,0 +1,184 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using static Vanara.PInvoke.Kernel32;
|
||||
|
||||
namespace Vanara.Diagnostics
|
||||
{
|
||||
/// <summary>Represents a system I/O completion port.</summary>
|
||||
/// <remarks>To use this class, create an instance with the <see cref="Create"/> method and then add one or more handlers.</remarks>
|
||||
/// <seealso cref="System.IDisposable"/>
|
||||
public class IoCompletionPort : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<UIntPtr, Action<uint, UIntPtr, IntPtr>> handlers = new ConcurrentDictionary<UIntPtr, Action<uint, UIntPtr, IntPtr>>();
|
||||
private bool disposedValue = false;
|
||||
private HANDLE hComplPort;
|
||||
|
||||
private IoCompletionPort(HANDLE hValidPort)
|
||||
{
|
||||
hComplPort = hValidPort;
|
||||
Task.Factory.StartNew(PollCompletionPortThread);
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="IoCompletionPort"/> class.</summary>
|
||||
~IoCompletionPort() => Dispose(false);
|
||||
|
||||
/// <summary>Gets the handle for the I/O completion port.</summary>
|
||||
/// <value>The handle.</value>
|
||||
public IntPtr Handle => (IntPtr)hComplPort;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an input/output (I/O) completion port that is not yet associated with a file handle, allowing association at a later time.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IoCompletionPort"/> instance.</returns>
|
||||
public static IoCompletionPort Create()
|
||||
{
|
||||
var hComplPort = CreateIoCompletionPort((IntPtr)HFILE.INVALID_HANDLE_VALUE, HANDLE.NULL, default, 0);
|
||||
if (hComplPort.IsNull)
|
||||
Win32Error.ThrowLastError();
|
||||
|
||||
return new IoCompletionPort(hComplPort);
|
||||
}
|
||||
|
||||
/// <summary>Adds key and handler to the I/O completion port.</summary>
|
||||
/// <param name="key">A unique completion key to be passed to the handler when called.</param>
|
||||
/// <param name="handler">An action to perform when an I/O operation is complete.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The value for <paramref name="key"/> cannot be <c>UIntPtr.Zero</c>.</exception>
|
||||
/// <exception cref="InvalidOperationException">Key already exists.</exception>
|
||||
public void AddKeyHandler(UIntPtr key, Action<uint, UIntPtr, IntPtr> handler)
|
||||
{
|
||||
if (key == UIntPtr.Zero)
|
||||
throw new ArgumentOutOfRangeException(nameof(key), "Key value cannot be 0.");
|
||||
|
||||
if (!handlers.TryAdd(key, handler))
|
||||
throw new InvalidOperationException("Key already exists.");
|
||||
}
|
||||
|
||||
/// <summary>Adds an overlapped handle, key and handler to the I/O completion port.</summary>
|
||||
/// <param name="overlappedHandle">
|
||||
/// An open handle to an object that supports overlapped I/O.
|
||||
/// <para>
|
||||
/// The provided handle has to have been opened for overlapped I/O completion. For example, you must specify the FILE_FLAG_OVERLAPPED
|
||||
/// flag when using the CreateFile function to obtain the handle.
|
||||
/// </para>
|
||||
/// </param>
|
||||
/// <param name="key">A unique completion key to be passed to the handler when called.</param>
|
||||
/// <param name="handler">An action to perform when an I/O operation is complete.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The value for <paramref name="key"/> cannot be <c>UIntPtr.Zero</c>.</exception>
|
||||
/// <exception cref="InvalidOperationException">Key already exists.</exception>
|
||||
public void AddKeyHandler(IntPtr overlappedHandle, UIntPtr key, Action<uint, UIntPtr, IntPtr> handler)
|
||||
{
|
||||
AddKeyHandler(key, handler);
|
||||
|
||||
if (CreateIoCompletionPort(overlappedHandle, hComplPort, key, 0).IsNull)
|
||||
Win32Error.ThrowLastError();
|
||||
}
|
||||
|
||||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>Posts an I/O completion packet to an I/O completion port.</summary>
|
||||
/// <param name="completionKey">
|
||||
/// The value to be returned through the lpCompletionKey parameter of the <c>GetQueuedCompletionStatus</c> function.
|
||||
/// </param>
|
||||
/// <param name="numberOfBytesTransferred">
|
||||
/// The value to be returned through the lpNumberOfBytesTransferred parameter of the <c>GetQueuedCompletionStatus</c> function.
|
||||
/// </param>
|
||||
/// <param name="lpOverlapped">
|
||||
/// The value to be returned through the lpOverlapped parameter of the <c>GetQueuedCompletionStatus</c> function.
|
||||
/// </param>
|
||||
public void PostQueuedStatus(UIntPtr completionKey, uint numberOfBytesTransferred = 0, IntPtr lpOverlapped = default)
|
||||
{
|
||||
if (completionKey == UIntPtr.Zero)
|
||||
throw new ArgumentOutOfRangeException(nameof(completionKey), "Key value cannot be 0.");
|
||||
|
||||
if (!PostQueuedCompletionStatus(hComplPort, numberOfBytesTransferred, completionKey, lpOverlapped))
|
||||
Win32Error.ThrowLastError();
|
||||
}
|
||||
|
||||
/// <summary>Posts an I/O completion packet to an I/O completion port.</summary>
|
||||
/// <param name="completionKey">
|
||||
/// The value to be returned through the lpCompletionKey parameter of the <c>GetQueuedCompletionStatus</c> function.
|
||||
/// </param>
|
||||
/// <param name="numberOfBytesTransferred">
|
||||
/// The value to be returned through the lpNumberOfBytesTransferred parameter of the <c>GetQueuedCompletionStatus</c> function.
|
||||
/// </param>
|
||||
/// <param name="lpOverlapped">
|
||||
/// The value to be returned through the lpOverlapped parameter of the <c>GetQueuedCompletionStatus</c> function.
|
||||
/// </param>
|
||||
public unsafe void PostQueuedStatus(UIntPtr completionKey, uint numberOfBytesTransferred, NativeOverlapped* lpOverlapped)
|
||||
{
|
||||
if (completionKey == UIntPtr.Zero)
|
||||
throw new ArgumentOutOfRangeException(nameof(completionKey), "Key value cannot be 0.");
|
||||
|
||||
if (!PostQueuedCompletionStatus(hComplPort, numberOfBytesTransferred, completionKey, lpOverlapped))
|
||||
Win32Error.ThrowLastError();
|
||||
}
|
||||
|
||||
/// <summary>Removes the handler associated with <paramref name="key"/>.</summary>
|
||||
/// <param name="key">The key of the handler to remove.</param>
|
||||
/// <exception cref="InvalidOperationException">Key does not exist.</exception>
|
||||
public void RemoveKeyHandler(UIntPtr key)
|
||||
{
|
||||
if (!handlers.TryRemove(key, out _))
|
||||
throw new InvalidOperationException("Key does not exist.");
|
||||
}
|
||||
|
||||
/// <summary>Releases unmanaged and - optionally - managed resources.</summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects).
|
||||
}
|
||||
|
||||
if (!hComplPort.IsNull)
|
||||
{
|
||||
// Shut down background thread processing completion port messages.
|
||||
PostQueuedCompletionStatus(hComplPort, 0);
|
||||
|
||||
// Close the completion port handle
|
||||
CloseHandle((IntPtr)hComplPort);
|
||||
hComplPort = HANDLE.NULL;
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void PollCompletionPortThread()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Wait forever to get the next completion status
|
||||
if (!GetQueuedCompletionStatus(hComplPort, out var byteCount, out var completionKey, out var overlapped, INFINITE) && overlapped == IntPtr.Zero)
|
||||
{
|
||||
var err = Win32Error.GetLastError();
|
||||
if (err == Win32Error.ERROR_ABANDONED_WAIT_0)
|
||||
break;
|
||||
throw err.GetException();
|
||||
}
|
||||
|
||||
// End the thread if terminating completion key signals
|
||||
if (byteCount == 0 && completionKey == UIntPtr.Zero && overlapped == IntPtr.Zero)
|
||||
break;
|
||||
|
||||
// Spin this off so we don't hang the completion port.
|
||||
if (handlers.TryGetValue(completionKey, out var action))
|
||||
Task.Factory.StartNew(o => { if (o is Tuple<uint, UIntPtr, IntPtr> t) action(t.Item1, t.Item2, t.Item3); }, new Tuple<uint, UIntPtr, IntPtr>(byteCount, completionKey, overlapped));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -36,6 +36,7 @@ BackgroundCopyACLFlags, BackgroundCopyCost, BackgroundCopyErrorContext, Backgrou
|
|||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\Vanara.snk</AssemblyOriginatorKeyFile>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
|
|
|
@ -12,6 +12,37 @@ namespace Vanara.PInvoke.Tests
|
|||
[TestFixture]
|
||||
public class JobApiTests
|
||||
{
|
||||
[Test]
|
||||
public void ClearSetValueTest()
|
||||
{
|
||||
using var job = CreateJobObject();
|
||||
|
||||
var bi = QueryInformationJobObject<JOBOBJECT_BASIC_LIMIT_INFORMATION>(job, JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation);
|
||||
Assert.That(bi.LimitFlags, Is.EqualTo((JOBOBJECT_LIMIT_FLAGS)0));
|
||||
Assert.That(bi.ActiveProcessLimit, Is.Zero);
|
||||
bi.WriteValues();
|
||||
|
||||
bi.ActiveProcessLimit = 2U;
|
||||
bi.Affinity = (UIntPtr)0xfU;
|
||||
bi.LimitFlags |= JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_ACTIVE_PROCESS | JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_AFFINITY;
|
||||
Assert.That(() => SetInformationJobObject(job, JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation, bi), Throws.Nothing);
|
||||
|
||||
var bi2 = QueryInformationJobObject<JOBOBJECT_BASIC_LIMIT_INFORMATION>(job, JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation);
|
||||
Assert.That(bi2.LimitFlags, Is.EqualTo(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_ACTIVE_PROCESS | JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_AFFINITY));
|
||||
Assert.That(bi2.ActiveProcessLimit, Is.Not.Zero);
|
||||
Assert.That(bi2.Affinity, Is.EqualTo((UIntPtr)0xfU));
|
||||
bi2.WriteValues();
|
||||
|
||||
bi2.ActiveProcessLimit = 0;
|
||||
bi2.LimitFlags &= ~JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
|
||||
Assert.That(() => SetInformationJobObject(job, JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation, bi2), Throws.Nothing);
|
||||
|
||||
var bi3 = QueryInformationJobObject<JOBOBJECT_BASIC_LIMIT_INFORMATION>(job, JOBOBJECTINFOCLASS.JobObjectBasicLimitInformation);
|
||||
Assert.That(bi3.LimitFlags.IsFlagSet(JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_ACTIVE_PROCESS), Is.False);
|
||||
Assert.That(bi3.ActiveProcessLimit, Is.Zero);
|
||||
bi3.WriteValues();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateAssignChcekJobObjectTest()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
using NUnit.Framework;
|
||||
using NUnit.Framework.Internal;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Vanara.Extensions;
|
||||
using Vanara.InteropServices;
|
||||
using Vanara.PInvoke;
|
||||
using Vanara.PInvoke.Tests;
|
||||
using static Vanara.PInvoke.Kernel32;
|
||||
|
||||
namespace Vanara.Diagnostics.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class JobTests
|
||||
{
|
||||
[Test]
|
||||
public void ActiveProcessLimitTest()
|
||||
{
|
||||
using var job = Job.Create();
|
||||
|
||||
job.RuntimeLimits.ActiveProcessLimit = 2;
|
||||
Assert.That(job.RuntimeLimits.ActiveProcessLimit.Value, Is.EqualTo(2U));
|
||||
job.RuntimeLimits.ActiveProcessLimit = 0;
|
||||
Assert.That(job.RuntimeLimits.ActiveProcessLimit.Value, Is.EqualTo(0U));
|
||||
Assert.That(() => job.StartProcess("notepad.exe"), Throws.Exception);
|
||||
job.RuntimeLimits.ActiveProcessLimit = null;
|
||||
Assert.That(job.RuntimeLimits.ActiveProcessLimit.HasValue, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EventsTest()
|
||||
{
|
||||
using var job = Job.Create(Environment.UserName);
|
||||
try
|
||||
{
|
||||
job.NewProcess += msgHndlr;
|
||||
job.ProcessExited += msgHndlr;
|
||||
job.JobMemoryLimitExceeded += msgHndlr;
|
||||
job.JobNotificationLimitExceeded += notHndlr;
|
||||
|
||||
var startInfo = new ProcessStartInfo("notepad.exe") { UseShellExecute = false };
|
||||
var p1 = new Process { StartInfo = startInfo };
|
||||
p1.StartEx(CREATE_PROCESS.CREATE_SUSPENDED);
|
||||
job.AssignProcess(p1);
|
||||
p1.ResumePrimaryThread();
|
||||
|
||||
Thread.Sleep(200);
|
||||
|
||||
job.Notifications.JobMemoryLimit = job.Statistics.PeakJobMemoryUsed;
|
||||
job.RuntimeLimits.JobMemoryLimit = job.Notifications.JobMemoryLimit * 3;
|
||||
//TestContext.WriteLine($"JobMemory: NotLim: {job.JobMemory.NotificationLimit}, Lim: {job.JobMemory.Limit}");
|
||||
|
||||
//job.PerJobUserTime.NotificationLimit = job.Statistics.TotalUserTime;
|
||||
//job.PerJobUserTime.Limit = TimeSpan.FromTicks(job.PerJobUserTime.NotificationLimit.Value.Ticks * 3);
|
||||
//TestContext.WriteLine($"PerJobUserTime: NotLim: {job.PerJobUserTime.NotificationLimit}, Lim: {job.PerJobUserTime.Limit}");
|
||||
|
||||
var p2 = new Process { StartInfo = startInfo };
|
||||
p2.StartEx(CREATE_PROCESS.CREATE_SUSPENDED);
|
||||
job.AssignProcess(p2);
|
||||
p2.ResumePrimaryThread();
|
||||
|
||||
Thread.Sleep(2000);
|
||||
Thread.Yield();
|
||||
|
||||
p1.Kill();
|
||||
Thread.Sleep(200);
|
||||
Thread.Yield();
|
||||
}
|
||||
finally
|
||||
{
|
||||
job.TerminateAllProcesses(0);
|
||||
}
|
||||
|
||||
static void msgHndlr(object s, JobEventArgs e) => TestContext.WriteLine($"{DateTime.Now:u}: {e.JobMessage}, {e.ProcessId}");
|
||||
static void notHndlr(object s, JobNotificationEventArgs e) => TestContext.WriteLine($"{DateTime.Now:u}: {e.JobMessage}, {e.Limit}, Limit: {e.NotificationLimit}, Val: {e.ReportedValue}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LimitsTest()
|
||||
{
|
||||
using var job = Job.Create();
|
||||
try
|
||||
{
|
||||
Assert.That(job.Processes.Count, Is.EqualTo(0));
|
||||
var p1 = TestHelper.RunThrottleApp();
|
||||
job.AssignProcess(p1);
|
||||
Thread.Sleep(200);
|
||||
|
||||
job.RuntimeLimits.ActiveProcessLimit = 1;
|
||||
Assert.That(() => job.StartProcess("notepad.exe"), Throws.Exception);
|
||||
job.RuntimeLimits.ActiveProcessLimit = null;
|
||||
|
||||
Test(job.RuntimeLimits, 1.0, (s, v) => s.CpuRateLimit = v, s => s.CpuRateLimit, true);
|
||||
Test(job.RuntimeLimits, (25.0, 75.0), (s, v) => s.CpuRatePortion = v, s => s.CpuRatePortion, true);
|
||||
Test(job.RuntimeLimits, 3, (s, v) => s.CpuRateRelativeWeight = v, s => s.CpuRateRelativeWeight, true);
|
||||
Test(job.RuntimeLimits, 4096UL, (s, v) => s.JobMemoryLimit = v, s => s.JobMemoryLimit, true);
|
||||
Test(job.RuntimeLimits, 4096UL, (s, v) => s.MaxBandwidth = v, s => s.MaxBandwidth);
|
||||
TestGT(job.RuntimeLimits, TimeSpan.FromTicks(30000), (s, v) => s.PerJobUserTimeLimit = v, s => s.PerJobUserTimeLimit);
|
||||
TestGT(job.RuntimeLimits, TimeSpan.FromTicks(30000), (s, v) => s.PerProcessUserTimeLimit = v, s => s.PerProcessUserTimeLimit);
|
||||
Test(job.RuntimeLimits, 4096UL, (s, v) => s.ProcessMemoryLimit = v, s => s.ProcessMemoryLimit, true);
|
||||
var pmc = PROCESS_MEMORY_COUNTERS.Default;
|
||||
GetProcessMemoryInfo(p1, out pmc, pmc.cb);
|
||||
Test(job.RuntimeLimits, (pmc.WorkingSetSize, pmc.PeakWorkingSetSize), (s, v) => s.WorkingSetSize = v, s => s.WorkingSetSize, true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
job.TerminateAllProcesses(0);
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
|
||||
void TestGT<T, TS>(TS js, T? value, Action<TS, T?> set, Func<TS, T?> get) where T : struct
|
||||
{
|
||||
Assert.That(() => set(js, value), Throws.Nothing);
|
||||
Assert.That(get(js).Value, Is.GreaterThanOrEqualTo(value));
|
||||
|
||||
Assert.That(() => set(js, null), Throws.Nothing);
|
||||
Assert.That(get(js), Is.Null);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NotificationTest()
|
||||
{
|
||||
using var job = Job.Create();
|
||||
try
|
||||
{
|
||||
job.NewProcess += (s, e) => TestContext.WriteLine($"{DateTime.Now:u}: {e.JobMessage}, {e.ProcessId}");
|
||||
job.JobNotificationLimitExceeded += notHndlr;
|
||||
|
||||
job.Notifications.IoRateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceLow;
|
||||
job.Notifications.IoRateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort;
|
||||
//job.Notifications.IoReadBytesLimit = 1;
|
||||
//job.Notifications.IoWriteBytesLimit = 1;
|
||||
job.Notifications.JobMemoryLimit = 8092;
|
||||
job.Notifications.JobLowMemoryLimit = 4096;
|
||||
job.Notifications.NetRateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceLow;
|
||||
job.Notifications.NetRateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort;
|
||||
//job.Notifications.PerJobUserTimeLimit = TimeSpan.FromMilliseconds(1);
|
||||
//job.Notifications.RateControlTolerance = JOBOBJECT_RATE_CONTROL_TOLERANCE.ToleranceLow;
|
||||
//job.Notifications.RateControlToleranceInterval = JOBOBJECT_RATE_CONTROL_TOLERANCE_INTERVAL.ToleranceIntervalShort;
|
||||
|
||||
job.StartProcess("notepad.exe");
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
Thread.Yield();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
job.TerminateAllProcesses(0);
|
||||
}
|
||||
|
||||
static void notHndlr(object s, JobNotificationEventArgs e) => TestContext.WriteLine($"{DateTime.Now:u}: {e.JobMessage}, {e.Limit}, Limit: {e.NotificationLimit}, Val: {e.ReportedValue}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OpenJobTest()
|
||||
{
|
||||
using var job = Job.Create(Environment.UserName);
|
||||
Assert.That(job.Handle, ResultIs.ValidHandle);
|
||||
//using var job1 = Job.Open(Environment.UserName, JobAccessRight.JOB_OBJECT_QUERY);
|
||||
//Assert.That(job1.Handle, ResultIs.ValidHandle);
|
||||
|
||||
Assert.That(() => job.Settings.GroupAffinity, Throws.Nothing);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProcessesTest()
|
||||
{
|
||||
Process p2 = null;
|
||||
using (var job = Job.Create())
|
||||
{
|
||||
job.Settings.KillOnJobClose = true;
|
||||
|
||||
var curProc = Process.GetCurrentProcess();
|
||||
Assert.That(job.ContainsProcess(curProc), Is.False);
|
||||
|
||||
var p1 = Process.Start("notepad.exe");
|
||||
job.AssignProcess(p1);
|
||||
job.AssignProcess(p2 = Process.Start("notepad.exe"));
|
||||
|
||||
Assert.That(job.Processes.Count, Is.EqualTo(2));
|
||||
Assert.That(job.Processes.Count(), Is.EqualTo(2));
|
||||
Assert.That(job.Processes.First().Id, Is.EqualTo(p1.Id));
|
||||
|
||||
p1.Kill();
|
||||
Assert.That(p1.WaitForExit(500), Is.True);
|
||||
Assert.That(job.Processes.Count, Is.EqualTo(1));
|
||||
Assert.That(job.Processes.Count(), Is.EqualTo(1));
|
||||
}
|
||||
Assert.That(p2.WaitForExit(500), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SettingsTest()
|
||||
{
|
||||
using var job = Job.Create();
|
||||
|
||||
Test(job.Settings, new UIntPtr(0xf), (s, v) => s.Affinity = v, s => s.Affinity, true);
|
||||
TestBool(job.Settings, (s, v) => s.ChildProcessBreakawayAllowed = v, s => s.ChildProcessBreakawayAllowed);
|
||||
TestBool(job.Settings, (s, v) => s.ChildProcessSilentBreakawayAllowed = v, s => s.ChildProcessSilentBreakawayAllowed);
|
||||
TestBool(job.Settings, (s, v) => s.DieOnUnhandledException = v, s => s.DieOnUnhandledException);
|
||||
Test(job.Settings, 0xf, (s, v) => s.DscpTag = v, s => s.DscpTag);
|
||||
TestBool(job.Settings, (s, v) => s.KillOnJobClose = v, s => s.KillOnJobClose);
|
||||
Test(job.Settings, ProcessPriorityClass.BelowNormal, (s, v) => s.PriorityClass = v, s => s.PriorityClass, true);
|
||||
Test(job.Settings, 3, (s, v) => s.SchedulingClass = v, s => s.SchedulingClass);
|
||||
TestBool(job.Settings, (s, v) => s.TerminateProcessesAtEndOfJobTimeLimit = v, s => s.TerminateProcessesAtEndOfJobTimeLimit);
|
||||
Assert.That(() => job.Settings.UIRestrictionsClass = JOBOBJECT_UILIMIT_FLAGS.JOB_OBJECT_UILIMIT_ALL, Throws.Nothing);
|
||||
Assert.That(job.Settings.UIRestrictionsClass, Is.EqualTo(JOBOBJECT_UILIMIT_FLAGS.JOB_OBJECT_UILIMIT_ALL));
|
||||
|
||||
Assert.That(() => job.Settings.GroupAffinity.First(), Throws.Nothing);
|
||||
Assert.That(job.Settings.GroupAffinity.First().Mask.ToUInt32(), Is.Not.Zero);
|
||||
Assert.That(() => job.Settings.GroupAffinity = job.Settings.GroupAffinity, Throws.Nothing);
|
||||
}
|
||||
|
||||
private void Test<T, TS>(TS js, T? value, Action<TS, T?> set, Func<TS, T?> get, bool ignDef = false) where T : struct
|
||||
{
|
||||
Assert.That(() => set(js, value), Throws.Nothing);
|
||||
Assert.That(get(js).Value, Is.EqualTo(value));
|
||||
|
||||
if (!ignDef)
|
||||
{
|
||||
Assert.That(() => set(js, default(T)), Throws.Nothing);
|
||||
Assert.That(get(js).Value, Is.EqualTo(default(T)));
|
||||
}
|
||||
|
||||
Assert.That(() => set(js, null), Throws.Nothing);
|
||||
Assert.That(get(js), Is.Null);
|
||||
}
|
||||
|
||||
private void TestBool<TS>(TS js, Action<TS, bool> set, Func<TS, bool> get)
|
||||
{
|
||||
var orig = get(js);
|
||||
|
||||
Assert.That(() => set(js, !orig), Throws.Nothing);
|
||||
Assert.That(get(js), Is.EqualTo(!orig));
|
||||
|
||||
Assert.That(() => set(js, orig), Throws.Nothing);
|
||||
Assert.That(get(js), Is.EqualTo(orig));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StatsTest()
|
||||
{
|
||||
using var job = Job.Create();
|
||||
try
|
||||
{
|
||||
job.AssignProcess(Process.Start("notepad.exe"));
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
finally
|
||||
{
|
||||
job.TerminateAllProcesses(0);
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
var stats = job.Statistics;
|
||||
stats.WriteValues();
|
||||
Assert.That(stats.PeakJobMemoryUsed, Is.GreaterThan(0UL));
|
||||
Assert.That(stats.PeakProcessMemoryUsed, Is.GreaterThan(0UL));
|
||||
Assert.That(stats.ThisPeriodTotalKernelTime.Ticks, Is.GreaterThan(0UL));
|
||||
Assert.That(stats.ThisPeriodTotalUserTime.Ticks, Is.GreaterThanOrEqualTo(0UL));
|
||||
Assert.That(stats.TotalKernelTime.Ticks, Is.GreaterThan(0UL));
|
||||
Assert.That(stats.TotalPageFaultCount, Is.GreaterThanOrEqualTo(0UL));
|
||||
Assert.That(stats.TotalProcesses, Is.GreaterThan(0UL));
|
||||
Assert.That(stats.TotalTerminatedProcesses, Is.GreaterThanOrEqualTo(0UL));
|
||||
Assert.That(stats.TotalUserTime.Ticks, Is.GreaterThanOrEqualTo(0UL));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TempTest()
|
||||
{
|
||||
using var job = Job.Create();
|
||||
var str = new JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2 { LimitFlags = JOBOBJECT_LIMIT_FLAGS.JOB_OBJECT_LIMIT_JOB_MEMORY_LOW, JobLowMemoryLimit = 4096 };
|
||||
using var mem = SafeHGlobalHandle.CreateFromStructure(str);
|
||||
if (!SetInformationJobObject(job, JOBOBJECTINFOCLASS.JobObjectNotificationLimitInformation2, mem, mem.Size))
|
||||
TestContext.WriteLine($"{Win32Error.GetLastError()}");
|
||||
|
||||
mem.Zero();
|
||||
if (QueryInformationJobObject(job, JOBOBJECTINFOCLASS.JobObjectNotificationLimitInformation2, mem, mem.Size, out _))
|
||||
mem.ToStructure<JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2>().WriteValues();
|
||||
|
||||
str.LimitFlags = 0;
|
||||
str.JobLowMemoryLimit = 0;
|
||||
mem.Write(str);
|
||||
if (!SetInformationJobObject(job, JOBOBJECTINFOCLASS.JobObjectNotificationLimitInformation2, mem, mem.Size))
|
||||
TestContext.WriteLine($"{Win32Error.GetLastError()}");
|
||||
|
||||
mem.Zero();
|
||||
if (QueryInformationJobObject(job, JOBOBJECTINFOCLASS.JobObjectNotificationLimitInformation2, mem, mem.Size, out _))
|
||||
mem.ToStructure<JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION_2>().WriteValues();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
|
@ -30,17 +31,17 @@
|
|||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BackgroundCopyTests.cs" />
|
||||
<Compile Include="ComputerTests.cs" />
|
||||
<Compile Include="NetworkListTests.cs" />
|
||||
<Compile Include="JobTests.cs" />
|
||||
<Compile Include="PowerTests.cs" />
|
||||
<Compile Include="RegTests.cs" />
|
||||
<Compile Include="VirtualDiskTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NUnit">
|
||||
|
|
18
Vanara.sln
18
Vanara.sln
|
@ -173,6 +173,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vanara.BITS", "BITS\Vanara.
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vanara.VirtualDisk", "VirtualDisk\Vanara.VirtualDisk.csproj", "{D4E36942-7492-46B9-985B-F99D8F5A35AB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BITS", "UnitTests\BITS\BITS.csproj", "{5558B8E3-FF1C-401F-978E-E91D1E78B898}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualDisk", "UnitTests\VirtualDisk\VirtualDisk.csproj", "{687F9162-8CA0-4277-B868-4E7F2EC614F8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug (no Unit Tests)|Any CPU = Debug (no Unit Tests)|Any CPU
|
||||
|
@ -565,6 +569,18 @@ Global
|
|||
{D4E36942-7492-46B9-985B-F99D8F5A35AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4E36942-7492-46B9-985B-F99D8F5A35AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D4E36942-7492-46B9-985B-F99D8F5A35AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5558B8E3-FF1C-401F-978E-E91D1E78B898}.Debug (no Unit Tests)|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5558B8E3-FF1C-401F-978E-E91D1E78B898}.Debug (no Unit Tests)|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5558B8E3-FF1C-401F-978E-E91D1E78B898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5558B8E3-FF1C-401F-978E-E91D1E78B898}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5558B8E3-FF1C-401F-978E-E91D1E78B898}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5558B8E3-FF1C-401F-978E-E91D1E78B898}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{687F9162-8CA0-4277-B868-4E7F2EC614F8}.Debug (no Unit Tests)|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{687F9162-8CA0-4277-B868-4E7F2EC614F8}.Debug (no Unit Tests)|Any CPU.Build.0 = Debug|Any CPU
|
||||
{687F9162-8CA0-4277-B868-4E7F2EC614F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{687F9162-8CA0-4277-B868-4E7F2EC614F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{687F9162-8CA0-4277-B868-4E7F2EC614F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{687F9162-8CA0-4277-B868-4E7F2EC614F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -639,6 +655,8 @@ Global
|
|||
{8BC51B6B-77FA-4571-8E1C-EA75ED4DCD56} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
|
||||
{C1D9429F-A8BD-4094-ABE3-32581FC4614F} = {212ABBD0-B724-4CFA-9D6D-E3891547FA90}
|
||||
{15189584-3BD8-47DB-8B65-F58482063585} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
|
||||
{5558B8E3-FF1C-401F-978E-E91D1E78B898} = {3EC6B40D-71D3-4E59-A0E0-544EC605FE11}
|
||||
{687F9162-8CA0-4277-B868-4E7F2EC614F8} = {3EC6B40D-71D3-4E59-A0E0-544EC605FE11}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {543FAC75-2AF1-4EF1-9609-B242B63FEED4}
|
||||
|
|
Loading…
Reference in New Issue