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