Added .NET friendly wrappers for Job Objects and I/O Completion Ports with tests.

pull/83/head
David Hall 2019-10-17 11:30:18 -06:00
parent 8812c2cec3
commit c73424ae99
7 changed files with 2152 additions and 2 deletions

View File

@ -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));
}
}
}
}

1617
System/Diagnostics/Job.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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()
{

View File

@ -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();
}
}
}

View File

@ -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">

View File

@ -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}