diff --git a/src/dotnet/FileSystemHost.cs b/src/dotnet/FileSystemHost.cs
index 32481aac..1c53a06f 100644
--- a/src/dotnet/FileSystemHost.cs
+++ b/src/dotnet/FileSystemHost.cs
@@ -386,6 +386,89 @@ namespace Fsp
{
return Api.GetVersion();
}
+ ///
+ /// Returns a RequestHint to reference the current operation asynchronously.
+ ///
+ public UInt64 GetOperationRequestHint()
+ {
+ return Api.FspFileSystemGetOperationRequestHint();
+ }
+ ///
+ /// Asynchronously complete a Write operation.
+ ///
+ ///
+ /// A reference to the operation to complete.
+ ///
+ ///
+ /// STATUS_SUCCESS or error code.
+ ///
+ ///
+ /// The number of bytes written.
+ ///
+ ///
+ /// Updated file information.
+ ///
+ public void SendWriteResponse(UInt64 RequestHint, Int32 Status, UInt32 BytesTransferred, ref FileInfo FileInfo)
+ {
+ var Response = new FspFsctlTransactRsp()
+ {
+ Size = 128,
+ Kind = (UInt32) FspFsctlTransact.WriteKind,
+ Hint = RequestHint
+ };
+ Response.IoStatus.Information = BytesTransferred;
+ Response.IoStatus.Status = (UInt32) Status;
+ Response.WriteFileInfo = FileInfo;
+ Api.FspFileSystemSendResponse(_FileSystemPtr, ref Response);
+ }
+ ///
+ /// Asynchronously complete a Read operation.
+ ///
+ ///
+ /// A reference to the operation to complete.
+ ///
+ ///
+ /// STATUS_SUCCESS or error code.
+ ///
+ ///
+ /// Number of bytes read.
+ ///
+ public void SendReadResponse(UInt64 RequestHint, Int32 Status, UInt32 BytesTransferred)
+ {
+ var Response = new FspFsctlTransactRsp()
+ {
+ Size = 128,
+ Kind = (UInt32) FspFsctlTransact.ReadKind,
+ Hint = RequestHint
+ };
+ Response.IoStatus.Information = BytesTransferred;
+ Response.IoStatus.Status = (UInt32) Status;
+ Api.FspFileSystemSendResponse(_FileSystemPtr, ref Response);
+ }
+ ///
+ /// Asynchronously complete a ReadDirectory operation.
+ ///
+ ///
+ /// A reference to the operation to complete.
+ ///
+ ///
+ /// STATUS_SUCCESS or error code.
+ ///
+ ///
+ /// Number of bytes read.
+ ///
+ public void SendReadDirectoryResponse(UInt64 RequestHint, Int32 Status, UInt32 BytesTransferred)
+ {
+ var Response = new FspFsctlTransactRsp()
+ {
+ Size = 128,
+ Kind = (UInt32) FspFsctlTransact.QueryDirectoryKind,
+ Hint = RequestHint
+ };
+ Response.IoStatus.Information = BytesTransferred;
+ Response.IoStatus.Status = (UInt32) Status;
+ Api.FspFileSystemSendResponse(_FileSystemPtr, ref Response);
+ }
/* FSP_FILE_SYSTEM_INTERFACE */
private static Byte[] ByteBufferNotNull = new Byte[0];
diff --git a/src/dotnet/Interop.cs b/src/dotnet/Interop.cs
index 00ebf6a5..bb627a6b 100644
--- a/src/dotnet/Interop.cs
+++ b/src/dotnet/Interop.cs
@@ -355,6 +355,65 @@ namespace Fsp.Interop
public IntPtr Information;
}
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct IoStatus
+ {
+ internal UInt32 Information;
+ internal UInt32 Status;
+ }
+
+ internal enum FspFsctlTransact
+ {
+ ReadKind = 5,
+ WriteKind = 6,
+ QueryDirectoryKind = 14
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct FspFsctlTransactReq
+ {
+ [FieldOffset(0)]
+ internal UInt16 Version;
+ [FieldOffset(2)]
+ internal UInt16 Size;
+ [FieldOffset(4)]
+ internal UInt32 Kind;
+ [FieldOffset(8)]
+ internal UInt64 Hint;
+
+ [FieldOffset(0)]
+ internal unsafe fixed Byte Padding[88];
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct FspFsctlTransactRsp
+ {
+ [FieldOffset(0)]
+ internal UInt16 Version;
+ [FieldOffset(2)]
+ internal UInt16 Size;
+ [FieldOffset(4)]
+ internal UInt32 Kind;
+ [FieldOffset(8)]
+ internal UInt64 Hint;
+
+ [FieldOffset(16)]
+ internal IoStatus IoStatus;
+
+ [FieldOffset(24)]
+ internal FileInfo WriteFileInfo;
+
+ [FieldOffset(0)]
+ internal unsafe fixed Byte Padding[128];
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct FspFileSystemOperationContext
+ {
+ internal FspFsctlTransactReq *Request;
+ internal FspFsctlTransactRsp *Response;
+ }
+
[StructLayout(LayoutKind.Sequential)]
internal struct FileSystemInterface
{
@@ -664,6 +723,12 @@ namespace Fsp.Interop
internal delegate Int32 FspFileSystemStopDispatcher(
IntPtr FileSystem);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void FspFileSystemSendResponse(
+ IntPtr FileSystem,
+ ref FspFsctlTransactRsp Response);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal unsafe delegate FspFileSystemOperationContext *FspFileSystemGetOperationContext();
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate IntPtr FspFileSystemMountPointF(
IntPtr FileSystem);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@@ -848,6 +913,8 @@ namespace Fsp.Interop
internal static Proto.FspFileSystemRemoveMountPoint FspFileSystemRemoveMountPoint;
internal static Proto.FspFileSystemStartDispatcher FspFileSystemStartDispatcher;
internal static Proto.FspFileSystemStopDispatcher FspFileSystemStopDispatcher;
+ internal static Proto.FspFileSystemSendResponse FspFileSystemSendResponse;
+ internal static Proto.FspFileSystemGetOperationContext FspFileSystemGetOperationContext;
internal static Proto.FspFileSystemMountPointF FspFileSystemMountPoint;
internal static Proto.FspFileSystemSetOperationGuardStrategyF FspFileSystemSetOperationGuardStrategy;
internal static Proto.FspFileSystemSetDebugLogF FspFileSystemSetDebugLog;
@@ -894,6 +961,10 @@ namespace Fsp.Interop
else
return _FspFileSystemSetMountPointEx(FileSystem, MountPoint, IntPtr.Zero);
}
+ internal static unsafe UInt64 FspFileSystemGetOperationRequestHint()
+ {
+ return FspFileSystemGetOperationContext()->Request->Hint;
+ }
internal static unsafe Boolean FspFileSystemAddDirInfo(
ref DirInfo DirInfo,
IntPtr Buffer,
@@ -1242,6 +1313,8 @@ namespace Fsp.Interop
FspFileSystemRemoveMountPoint = GetEntryPoint(Module);
FspFileSystemStartDispatcher = GetEntryPoint(Module);
FspFileSystemStopDispatcher = GetEntryPoint(Module);
+ FspFileSystemSendResponse = GetEntryPoint(Module);
+ FspFileSystemGetOperationContext = GetEntryPoint(Module);
FspFileSystemMountPoint = GetEntryPoint(Module);
FspFileSystemSetOperationGuardStrategy = GetEntryPoint(Module);
FspFileSystemSetDebugLog = GetEntryPoint(Module);
diff --git a/tools/run-tests.bat b/tools/run-tests.bat
index c2199ef5..29fd5c3b 100755
--- a/tools/run-tests.bat
+++ b/tools/run-tests.bat
@@ -71,6 +71,7 @@ set dfl_tests=^
winfsp-tests-dotnet-external-share ^
fsx-memfs-dotnet-disk ^
fsx-memfs-dotnet-net ^
+ fsx-memfs-dotnet-slowio ^
winfstest-memfs-dotnet-disk ^
winfstest-memfs-dotnet-net
set opt_tests=^
@@ -498,6 +499,11 @@ call :__run_fsx_memfs_slowio_test memfs32-slowio memfs-x86
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
+:fsx-memfs-dotnet-slowio
+call :__run_fsx_memfs_slowio_test memfs.net-slowio memfs-dotnet-msil
+if !ERRORLEVEL! neq 0 goto fail
+exit /b 0
+
:__run_fsx_memfs_slowio_test
set RunSampleTestExit=0
call "%ProjRoot%\tools\fsreg" %1 "%ProjRoot%\build\VStudio\build\%Configuration%\%2.exe" "-u %%%%1 -m %%%%2 -M 50 -P 10 -R 5" "D:P(A;;RPWPLC;;;WD)"
diff --git a/tst/memfs-dotnet/Program.cs b/tst/memfs-dotnet/Program.cs
index c1d9a837..620438ae 100644
--- a/tst/memfs-dotnet/Program.cs
+++ b/tst/memfs-dotnet/Program.cs
@@ -19,12 +19,17 @@
* associated repository.
*/
+#define MEMFS_SLOWIO
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Threading;
+#if MEMFS_SLOWIO
+using System.Threading.Tasks;
+#endif
using Fsp;
using VolumeInfo = Fsp.Interop.VolumeInfo;
@@ -232,15 +237,20 @@ namespace memfs
class Memfs : FileSystemBase
{
+ private FileSystemHost Host;
public const UInt16 MEMFS_SECTOR_SIZE = 512;
public const UInt16 MEMFS_SECTORS_PER_ALLOCATION_UNIT = 1;
public Memfs(
- Boolean CaseInsensitive, UInt32 MaxFileNodes, UInt32 MaxFileSize, String RootSddl)
+ Boolean CaseInsensitive, UInt32 MaxFileNodes, UInt32 MaxFileSize, String RootSddl,
+ UInt64 SlowioMaxDelay, UInt64 SlowioPercentDelay, UInt64 SlowioRarefyDelay)
{
this.FileNodeMap = new FileNodeMap(CaseInsensitive);
this.MaxFileNodes = MaxFileNodes;
this.MaxFileSize = MaxFileSize;
+ this.SlowioMaxDelay = SlowioMaxDelay;
+ this.SlowioPercentDelay = SlowioPercentDelay;
+ this.SlowioRarefyDelay = SlowioRarefyDelay;
/*
* Create root directory.
@@ -259,7 +269,7 @@ namespace memfs
public override Int32 Init(Object Host0)
{
- FileSystemHost Host = (FileSystemHost)Host0;
+ Host = (FileSystemHost)Host0;
Host.SectorSize = Memfs.MEMFS_SECTOR_SIZE;
Host.SectorsPerAllocationUnit = Memfs.MEMFS_SECTORS_PER_ALLOCATION_UNIT;
Host.VolumeCreationTime = (UInt64)DateTime.Now.ToFileTimeUtc();
@@ -278,6 +288,20 @@ namespace memfs
return STATUS_SUCCESS;
}
+#if MEMFS_SLOWIO
+ public override int Mounted(object Host)
+ {
+ SlowioTasksRunning = 0;
+ return STATUS_SUCCESS;
+ }
+
+ public override void Unmounted(object Host)
+ {
+ while (SlowioTasksRunning != 0)
+ Thread.Sleep(1000);
+ }
+#endif
+
public override Int32 GetVolumeInfo(
out VolumeInfo VolumeInfo)
{
@@ -561,6 +585,93 @@ namespace memfs
Interlocked.Decrement(ref FileNode.OpenCount);
}
+#if MEMFS_SLOWIO
+ private UInt64 Hash(UInt64 X)
+ {
+ X = (X ^ (X >> 30)) * 0xbf58476d1ce4e5b9ul;
+ X = (X ^ (X >> 27)) * 0x94d049bb133111ebul;
+ X = X ^ (X >> 31);
+ return X;
+ }
+
+ private static int Spin = 0;
+
+ private UInt64 PseudoRandom(UInt64 To)
+ {
+ /* John Oberschelp's PRNG */
+ Interlocked.Increment(ref Spin);
+ return Hash((UInt64)Spin) % To;
+ }
+
+ private bool SlowioReturnPending()
+ {
+ if (0 == SlowioMaxDelay)
+ {
+ return false;
+ }
+ return PseudoRandom(100) < SlowioPercentDelay;
+ }
+
+ private void SlowioSnooze()
+ {
+ double Millis = PseudoRandom(SlowioMaxDelay + 1) >> (int) PseudoRandom(SlowioRarefyDelay + 1);
+ Thread.Sleep(TimeSpan.FromMilliseconds(Millis));
+ }
+
+ private void SlowioReadTask(
+ Object FileNode0,
+ IntPtr Buffer,
+ UInt64 Offset,
+ UInt64 EndOffset,
+ UInt64 RequestHint)
+ {
+ SlowioSnooze();
+
+ UInt32 BytesTransferred = (UInt32)(EndOffset - Offset);
+ FileNode FileNode = (FileNode)FileNode0;
+ Marshal.Copy(FileNode.FileData, (int)Offset, Buffer, (int)BytesTransferred);
+
+ Host.SendReadResponse(RequestHint, STATUS_SUCCESS, BytesTransferred);
+ Interlocked.Decrement(ref SlowioTasksRunning);
+ }
+
+ private void SlowioWriteTask(
+ Object FileNode0,
+ IntPtr Buffer,
+ UInt64 Offset,
+ UInt64 EndOffset,
+ UInt64 RequestHint)
+ {
+ SlowioSnooze();
+
+ UInt32 BytesTransferred = (UInt32)(EndOffset - Offset);
+ FileNode FileNode = (FileNode)FileNode0;
+ FileInfo FileInfo = FileNode.GetFileInfo();
+ Marshal.Copy(Buffer, FileNode.FileData, (int)Offset, (int)BytesTransferred);
+
+ Host.SendWriteResponse(RequestHint, STATUS_SUCCESS, BytesTransferred, ref FileInfo);
+ Interlocked.Decrement(ref SlowioTasksRunning);
+ }
+
+ private void SlowioReadDirectoryTask(
+ Object FileNode0,
+ Object FileDesc,
+ String Pattern,
+ String Marker,
+ IntPtr Buffer,
+ UInt32 Length,
+ UInt64 RequestHint)
+ {
+ SlowioSnooze();
+
+ UInt32 BytesTransferred;
+ var Status = SeekableReadDirectory(FileNode0, FileDesc, Pattern, Marker, Buffer, Length, out BytesTransferred);
+
+ Host.SendReadDirectoryResponse(RequestHint, Status, BytesTransferred);
+ Interlocked.Decrement(ref SlowioTasksRunning);
+ }
+#endif
+
public override Int32 Read(
Object FileNode0,
Object FileDesc,
@@ -582,6 +693,25 @@ namespace memfs
if (EndOffset > FileNode.FileInfo.FileSize)
EndOffset = FileNode.FileInfo.FileSize;
+#if MEMFS_SLOWIO
+ if (SlowioReturnPending())
+ {
+ var Hint = Host.GetOperationRequestHint();
+ try
+ {
+ Interlocked.Increment(ref SlowioTasksRunning);
+ Task.Run(() => SlowioReadTask(FileNode0, Buffer, Offset, EndOffset, Hint)).ConfigureAwait(false);
+
+ BytesTransferred = 0;
+ return STATUS_PENDING;
+ }
+ catch (Exception)
+ {
+ Interlocked.Decrement(ref SlowioTasksRunning);
+ }
+ }
+#endif
+
BytesTransferred = (UInt32)(EndOffset - Offset);
Marshal.Copy(FileNode.FileData, (int)Offset, Buffer, (int)BytesTransferred);
@@ -631,6 +761,26 @@ namespace memfs
}
}
+#if MEMFS_SLOWIO
+ if (SlowioReturnPending())
+ {
+ var hint = Host.GetOperationRequestHint();
+ try
+ {
+ Interlocked.Increment(ref SlowioTasksRunning);
+ Task.Run(() => SlowioWriteTask(FileNode0, Buffer, Offset, EndOffset, hint)).ConfigureAwait(false);
+
+ BytesTransferred = 0;
+ FileInfo = default(FileInfo);
+ return STATUS_PENDING;
+ }
+ catch (Exception)
+ {
+ Interlocked.Decrement(ref SlowioTasksRunning);
+ }
+ }
+#endif
+
BytesTransferred = (UInt32)(EndOffset - Offset);
Marshal.Copy(Buffer, FileNode.FileData, (int)Offset, (int)BytesTransferred);
@@ -914,6 +1064,37 @@ namespace memfs
return false;
}
+#if MEMFS_SLOWIO
+ public override int ReadDirectory(
+ Object FileNode0,
+ Object FileDesc,
+ String Pattern,
+ String Marker,
+ IntPtr Buffer,
+ UInt32 Length,
+ out UInt32 BytesTransferred)
+ {
+ if (SlowioReturnPending())
+ {
+ var Hint = Host.GetOperationRequestHint();
+ try
+ {
+ Interlocked.Increment(ref SlowioTasksRunning);
+ Task.Run(() => SlowioReadDirectoryTask(FileNode0, FileDesc, Pattern, Marker, Buffer, Length, Hint));
+ BytesTransferred = 0;
+
+ return STATUS_PENDING;
+ }
+ catch (Exception)
+ {
+ Interlocked.Decrement(ref SlowioTasksRunning);
+ }
+ }
+
+ return SeekableReadDirectory(FileNode0, FileDesc, Pattern, Marker, Buffer, Length, out BytesTransferred);
+ }
+#endif
+
public override int GetDirInfoByName(
Object ParentNode0,
Object FileDesc,
@@ -1152,6 +1333,10 @@ namespace memfs
private FileNodeMap FileNodeMap;
private UInt32 MaxFileNodes;
private UInt32 MaxFileSize;
+ private UInt64 SlowioMaxDelay;
+ private UInt64 SlowioPercentDelay;
+ private UInt64 SlowioRarefyDelay;
+ private volatile Int32 SlowioTasksRunning;
private String VolumeLabel;
}
@@ -1182,6 +1367,9 @@ namespace memfs
UInt32 FileInfoTimeout = unchecked((UInt32)(-1));
UInt32 MaxFileNodes = 1024;
UInt32 MaxFileSize = 16 * 1024 * 1024;
+ UInt32 SlowioMaxDelay = 0;
+ UInt32 SlowioPercentDelay = 0;
+ UInt32 SlowioRarefyDelay = 0;
String FileSystemName = null;
String VolumePrefix = null;
String MountPoint = null;
@@ -1214,9 +1402,18 @@ namespace memfs
case 'm':
argtos(Args, ref I, ref MountPoint);
break;
+ case 'M':
+ argtol(Args, ref I, ref SlowioMaxDelay);
+ break;
case 'n':
argtol(Args, ref I, ref MaxFileNodes);
break;
+ case 'P':
+ argtol(Args, ref I, ref SlowioPercentDelay);
+ break;
+ case 'R':
+ argtol(Args, ref I, ref SlowioRarefyDelay);
+ break;
case 'S':
argtos(Args, ref I, ref RootSddl);
break;
@@ -1245,7 +1442,8 @@ namespace memfs
throw new CommandLineUsageException("cannot open debug log file");
Host = new FileSystemHost(Memfs = new Memfs(
- CaseInsensitive, MaxFileNodes, MaxFileSize, RootSddl));
+ CaseInsensitive, MaxFileNodes, MaxFileSize, RootSddl,
+ SlowioMaxDelay, SlowioPercentDelay, SlowioRarefyDelay));
Host.FileInfoTimeout = FileInfoTimeout;
Host.Prefix = VolumePrefix;
Host.FileSystemName = null != FileSystemName ? FileSystemName : "-MEMFS";
@@ -1274,6 +1472,9 @@ namespace memfs
" -t FileInfoTimeout [millis]\n" +
" -n MaxFileNodes\n" +
" -s MaxFileSize [bytes]\n" +
+ " -M MaxDelay [maximum slow IO delay in millis]\n" +
+ " -P PercentDelay [percent of slow IO to make pending]\n" +
+ " -R RarefyDelay [adjust the rarity of pending slow IO]\n" +
" -F FileSystemName\n" +
" -S RootSddl [file rights: FA, etc; NO generic rights: GA, etc.]\n" +
" -u \\Server\\Share [UNC prefix (single backslash)]\n" +