Fixed problem with VirtualDisk async methods not showing progress correctly (#317). Due to threading issue.

pull/328/head
David Hall 2022-09-20 19:29:19 -06:00
parent ca2f1369f0
commit a376a4531f
2 changed files with 35 additions and 49 deletions

View File

@ -21,7 +21,7 @@ namespace Vanara.IO.Tests
private static readonly string tmpfn = TestCaseSources.TempDirWhack + "TestVHD.vhd";
[Test]
public async Task CompactAsync1Test()
public async Task CompactAsyncTest()
{
using var vhd = VirtualDisk.Open(fn, false);
var rpt = new Reporter();
@ -32,7 +32,7 @@ namespace Vanara.IO.Tests
}
[Test]
public async Task CompactAsync2Test()
public async Task CompactAsyncMMITest()
{
using var vhd = VirtualDisk.Open(fn, false);
var rpt = new Reporter();
@ -97,7 +97,7 @@ namespace Vanara.IO.Tests
Assert.That(vhd.ParentPaths, Is.Not.Null.And.Length.EqualTo(1)); // must be differencing
Assert.That(vhd.ParentTimeStamp, Is.Not.Null); // must be differencing
Assert.That(vhd.PhysicalPath, Is.Null); // must be attached
Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x200));
Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x1000));
Assert.That(vhd.PhysicalSize, Is.LessThan(sz));
Assert.That(vhd.ProviderSubtype, Is.EqualTo(VirtualDisk.Subtype.Differencing));
Assert.That(vhd.ResilientChangeTrackingEnabled, Is.False);
@ -137,12 +137,11 @@ namespace Vanara.IO.Tests
Assert.That(vhd.MostRecentId, Is.Null.Or.Empty);
Assert.That(vhd.NewerChanges, Is.False);
Assert.That(vhd.ParentBackingStore, Is.Null); // must be attached
Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x200));
Assert.That(vhd.ParentIdentifier, Is.Null); // must be differencing
Assert.That(vhd.ParentPaths, Is.Null); // must be differencing
Assert.That(vhd.ParentTimeStamp, Is.Null); // must be differencing
Assert.That(vhd.PhysicalPath, Is.Null); // must be attached
Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x200));
Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x1000));
Assert.That(vhd.PhysicalSize, Is.LessThan(sz));
Assert.That(vhd.ProviderSubtype, Is.EqualTo(VirtualDisk.Subtype.Dynamic));
Assert.That(vhd.SectorSize, Is.EqualTo(0x200));

View File

@ -387,14 +387,16 @@ public partial class VirtualDisk : IDisposable, IHandle
throw new ArgumentNullException(nameof(path));
}
ManualResetEvent vhdOverlapEvent = new(false);
NativeOverlapped vhdOverlap = new() { EventHandle = vhdOverlapEvent.SafeWaitHandle.DangerousGetHandle() };
CreateVirtualDisk(storageType, path, mask, securityDescriptor, flags, 0, param, vhdOverlap, out SafeVIRTUAL_DISK_HANDLE hVhd).ThrowUnless(Win32Error.ERROR_IO_PENDING);
return await Task.Run(() =>
{
NativeOverlapped vhdOverlap = new();
CreateVirtualDisk(storageType, path, mask, securityDescriptor, flags, 0, param, vhdOverlap, out SafeVIRTUAL_DISK_HANDLE hVhd).ThrowUnless(Win32Error.ERROR_IO_PENDING);
if (!await GetProgressAsync(hVhd, vhdOverlap, cancellationToken, progress))
throw new OperationCanceledException(cancellationToken);
if (!GetProgress(hVhd, vhdOverlap, cancellationToken, progress))
throw new OperationCanceledException(cancellationToken);
return new VirtualDisk(hVhd, (OPEN_VIRTUAL_DISK_VERSION)param.Version, path);
return new VirtualDisk(hVhd, (OPEN_VIRTUAL_DISK_VERSION)param.Version, path);
});
}
/// <summary>Creates a virtual hard disk (VHD) image file, either using default parameters or using an existing VHD or physical disk.</summary>
@ -779,8 +781,8 @@ public partial class VirtualDisk : IDisposable, IHandle
if (!securityDescriptor.IsNull && !securityDescriptor.IsValidSecurityDescriptor())
throw new ArgumentException("Invalid security descriptor.");
await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) =>
AttachVirtualDisk(Handle, securityDescriptor, flags, 0, param ?? ATTACH_VIRTUAL_DISK_PARAMETERS.Default, vhdOverlap));
await RunAsync(Handle, (in NativeOverlapped vhdOverlap) =>
AttachVirtualDisk(Handle, securityDescriptor, flags, 0, param ?? ATTACH_VIRTUAL_DISK_PARAMETERS.Default, vhdOverlap), cancellationToken, progress);
}
/// <summary>
@ -892,8 +894,8 @@ public partial class VirtualDisk : IDisposable, IHandle
/// </param>
/// <returns><c>true</c> if operation completed without error or cancellation; <c>false</c> otherwise.</returns>
public async Task<bool> CompactAsync(CancellationToken cancellationToken = default, IProgress<int> progress = default) =>
await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) =>
CompactVirtualDisk(Handle, COMPACT_VIRTUAL_DISK_FLAG.COMPACT_VIRTUAL_DISK_FLAG_NONE, COMPACT_VIRTUAL_DISK_PARAMETERS.Default, vhdOverlap));
await RunAsync(Handle, (in NativeOverlapped vhdOverlap) =>
CompactVirtualDisk(Handle, COMPACT_VIRTUAL_DISK_FLAG.COMPACT_VIRTUAL_DISK_FLAG_NONE, COMPACT_VIRTUAL_DISK_PARAMETERS.Default, vhdOverlap), cancellationToken, progress);
/// <summary>Returns the value of the handle field.</summary>
/// <returns>An IntPtr representing the value of the handle field.</returns>
@ -954,7 +956,7 @@ public partial class VirtualDisk : IDisposable, IHandle
/// </param>
/// <returns><c>true</c> if operation completed without error or cancellation; <c>false</c> otherwise.</returns>
public async Task<bool> ExpandAsync(ulong newSize, CancellationToken cancellationToken = default, IProgress<int> progress = default) =>
await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) =>
await RunAsync(Handle, (in NativeOverlapped vhdOverlap) =>
{
if (ver < OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_2)
{
@ -963,8 +965,7 @@ public partial class VirtualDisk : IDisposable, IHandle
EXPAND_VIRTUAL_DISK_PARAMETERS param = new(newSize);
return ExpandVirtualDisk(Handle, EXPAND_VIRTUAL_DISK_FLAG.EXPAND_VIRTUAL_DISK_FLAG_NONE, param, vhdOverlap);
}
);
}, cancellationToken, progress);
/// <summary>Issues an embedded SCSI request directly to a virtual hard disk.</summary>
/// <param name="param">A valid RAW_SCSI_VIRTUAL_DISK_PARAMETERS structure that contains snapshot deletion data.</param>
@ -999,12 +1000,11 @@ public partial class VirtualDisk : IDisposable, IHandle
/// progress reporting.
/// </param>
public async Task MergeAsync(uint sourceDepth, uint targetDepth, CancellationToken cancellationToken = default, IProgress<int> progress = default) =>
await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) =>
await RunAsync(Handle, (in NativeOverlapped vhdOverlap) =>
{
MERGE_VIRTUAL_DISK_PARAMETERS param = new(sourceDepth, targetDepth);
return MergeVirtualDisk(Handle, MERGE_VIRTUAL_DISK_FLAG.MERGE_VIRTUAL_DISK_FLAG_NONE, param, vhdOverlap);
}
);
}, cancellationToken, progress);
/// <summary>Merges a child virtual hard disk (VHD) in a differencing chain with its immediate parent disk in the chain.</summary>
public void MergeWithParent()
@ -1032,13 +1032,13 @@ public partial class VirtualDisk : IDisposable, IHandle
if (ver < OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_2)
throw new NotSupportedException(@"Mirroring is only available to virtual disks opened under version 2 or higher.");
await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) =>
await RunAsync(Handle, (in NativeOverlapped vhdOverlap) =>
{
MIRROR_VIRTUAL_DISK_FLAG flags = File.Exists(destPath) ? MIRROR_VIRTUAL_DISK_FLAG.MIRROR_VIRTUAL_DISK_FLAG_EXISTING_FILE : MIRROR_VIRTUAL_DISK_FLAG.MIRROR_VIRTUAL_DISK_FLAG_NONE;
SafeCoTaskMemString pDest = new(destPath);
MIRROR_VIRTUAL_DISK_PARAMETERS param = new() { Version = MIRROR_VIRTUAL_DISK_VERSION.MIRROR_VIRTUAL_DISK_VERSION_1, Version1 = new() { MirrorVirtualDiskPath = pDest } };
return MirrorVirtualDisk(Handle, flags, param, vhdOverlap);
});
}, cancellationToken, progress);
}
/// <summary>Modifies the internal contents of a virtual disk file. Can be used to set the active leaf, or to fix up snapshot entries.</summary>
@ -1125,11 +1125,11 @@ public partial class VirtualDisk : IDisposable, IHandle
throw new NotSupportedException(@"Expansion is only available to virtual disks opened under version 2 or higher.");
}
return await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) =>
return await RunAsync(Handle, (in NativeOverlapped vhdOverlap) =>
{
RESIZE_VIRTUAL_DISK_PARAMETERS param = new(newSize);
return ResizeVirtualDisk(Handle, RESIZE_VIRTUAL_DISK_FLAG.RESIZE_VIRTUAL_DISK_FLAG_NONE, param, vhdOverlap);
});
}, cancellationToken, progress);
}
/// <summary>Sets the active leaf of a virtual disk file.</summary>
@ -1207,18 +1207,15 @@ public partial class VirtualDisk : IDisposable, IHandle
? output.DeviceNumber : null;
}
private static async Task<bool> GetProgressAsync(VIRTUAL_DISK_HANDLE phVhd, NativeOverlapped reset, CancellationToken cancellationToken = default, IProgress<int> progress = default)
private static bool GetProgress(VIRTUAL_DISK_HANDLE hVhd, in NativeOverlapped vhdOverlap, CancellationToken cancellationToken, IProgress<int> progress)
{
progress?.Report(0);
while (true)
{
Win32Error perr = GetVirtualDiskOperationProgress(phVhd, reset, out VIRTUAL_DISK_PROGRESS prog);
perr.ThrowIfFailed();
if (cancellationToken.IsCancellationRequested)
{
return false;
}
GetVirtualDiskOperationProgress(hVhd, vhdOverlap, out VIRTUAL_DISK_PROGRESS prog).ThrowIfFailed();
switch (prog.OperationStatus)
{
case 0:
@ -1226,19 +1223,14 @@ public partial class VirtualDisk : IDisposable, IHandle
return true;
case Win32Error.ERROR_IO_PENDING:
progress?.Report((int)(prog.CurrentValue * 100 / prog.CompletionValue));
progress?.Report(Math.Max(100, (int)(prog.CurrentValue * 100 / prog.CompletionValue)));
break;
default:
new Win32Error(prog.OperationStatus).ThrowIfFailed();
break;
}
if (prog.CurrentValue == prog.CompletionValue)
{
return true;
throw new Win32Error(prog.OperationStatus).GetException();
}
await Task.Delay(250, cancellationToken);
Task.Delay(100, cancellationToken);
}
}
@ -1268,18 +1260,13 @@ public partial class VirtualDisk : IDisposable, IHandle
private static SafeHFILE OpenDrive(string fn) => CreateFile(fn, 0, FileShare.ReadWrite, default, FileMode.Open, 0);
private static async Task<bool> RunAsync(CancellationToken cancellationToken, IProgress<int> progress, VIRTUAL_DISK_HANDLE hVhd, RunAsyncMethod method)
{
ManualResetEvent vhdOverlapEvent = new(false);
NativeOverlapped vhdOverlap = new() { EventHandle = vhdOverlapEvent.SafeWaitHandle.DangerousGetHandle() };
Win32Error err = method(in vhdOverlap);
if (err != Win32Error.ERROR_IO_PENDING)
private static async Task<bool> RunAsync(VIRTUAL_DISK_HANDLE hVhd, RunAsyncMethod method, CancellationToken cancellationToken, IProgress<int> progress) =>
await Task.Run(() =>
{
err.ThrowIfFailed();
}
return await GetProgressAsync(hVhd, vhdOverlap, cancellationToken, progress);
}
NativeOverlapped vhdOverlap = new();
method(in vhdOverlap).ThrowUnless(Win32Error.ERROR_IO_PENDING);
return GetProgress(hVhd, vhdOverlap, cancellationToken, progress);
});
private T GetInformation<T>(GET_VIRTUAL_DISK_INFO_VERSION info, long offset = 0)
{