From 5d4dac2f9fbbbf110af57bd0594d62aba3650d3c Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 4 Dec 2020 17:45:38 -0400 Subject: [PATCH] Make the HTTP.sys accept loop cheaper (#28345) * Make the HTTP.sys accept loop cheaper - Allocate a single AsyncAcceptContext per loop instead of one per request. - The AsyncAcceptContext now implements IValueTaskSource instead of Task and doesn't allocate a Task per request. - Use a preallocated overlapped since we don't have overlapping calls to accept for a single AsyncAcceptContext. - Remove some dead code from SafeNativeOverlapped * Clear the nativeRequestContext on dispose * PR feedback - Move NativeOverlapped* to the AsyncAcceptContext. This removes the SafeNativeOverlapped allocation as the lifetime is managed by the AsyncAcceptContext - Rename tcs * Send the error response before settings the result --- src/Servers/HttpSys/HttpSysServer.slnf | 28 +-- src/Servers/HttpSys/src/AsyncAcceptContext.cs | 184 +++++++++++------- src/Servers/HttpSys/src/HttpSysListener.cs | 32 +-- src/Servers/HttpSys/src/MessagePump.cs | 5 +- .../HttpSys/src/NativeInterop/HttpApi.cs | 3 +- .../FunctionalTests/Listener/Utilities.cs | 3 +- .../NativeInterop/SafeNativeOverlapped.cs | 15 -- .../RequestProcessing/NativeRequestContext.cs | 10 +- 8 files changed, 140 insertions(+), 140 deletions(-) diff --git a/src/Servers/HttpSys/HttpSysServer.slnf b/src/Servers/HttpSys/HttpSysServer.slnf index 0c5668f9e23..0252b81c657 100644 --- a/src/Servers/HttpSys/HttpSysServer.slnf +++ b/src/Servers/HttpSys/HttpSysServer.slnf @@ -1,29 +1,29 @@ -{ +{ "solution": { "path": "..\\..\\..\\AspNetCore.sln", - "projects" : [ - "src\\Servers\\HttpSys\\samples\\TestClient\\TestClient.csproj", - "src\\Servers\\HttpSys\\samples\\SelfHostServer\\SelfHostServer.csproj", - "src\\Servers\\HttpSys\\samples\\HotAddSample\\HotAddSample.csproj", - "src\\Servers\\HttpSys\\test\\FunctionalTests\\Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj", - "src\\Servers\\HttpSys\\test\\Tests\\Microsoft.AspNetCore.Server.HttpSys.Tests.csproj", - "src\\Servers\\HttpSys\\src\\Microsoft.AspNetCore.Server.HttpSys.csproj", + "projects": [ + "src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj", + "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", + "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", "src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj", "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", - "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj", "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", + "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj", "src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj", "src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj", - "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", - "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", + "src\\Servers\\HttpSys\\samples\\HotAddSample\\HotAddSample.csproj", "src\\Servers\\HttpSys\\samples\\QueueSharing\\QueueSharing.csproj", - "src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", - "src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj" + "src\\Servers\\HttpSys\\samples\\SelfHostServer\\SelfHostServer.csproj", + "src\\Servers\\HttpSys\\samples\\TestClient\\TestClient.csproj", + "src\\Servers\\HttpSys\\src\\Microsoft.AspNetCore.Server.HttpSys.csproj", + "src\\Servers\\HttpSys\\test\\FunctionalTests\\Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj", + "src\\Servers\\HttpSys\\test\\Tests\\Microsoft.AspNetCore.Server.HttpSys.Tests.csproj", + "src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" ] } -} +} \ No newline at end of file diff --git a/src/Servers/HttpSys/src/AsyncAcceptContext.cs b/src/Servers/HttpSys/src/AsyncAcceptContext.cs index c313f9124e9..712a1ed1b80 100644 --- a/src/Servers/HttpSys/src/AsyncAcceptContext.cs +++ b/src/Servers/HttpSys/src/AsyncAcceptContext.cs @@ -2,121 +2,135 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpSys.Internal; namespace Microsoft.AspNetCore.Server.HttpSys { - internal unsafe class AsyncAcceptContext : TaskCompletionSource, IDisposable + internal unsafe class AsyncAcceptContext : IValueTaskSource, IDisposable { - internal static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(IOWaitCallback); + private static readonly IOCompletionCallback IOCallback = IOWaitCallback; + private readonly PreAllocatedOverlapped _preallocatedOverlapped; + private NativeOverlapped* _overlapped; + + // mutable struct; do not make this readonly + private ManualResetValueTaskSourceCore _mrvts = new() + { + // We want to run continuations on the IO threads + RunContinuationsAsynchronously = false + }; private NativeRequestContext _nativeRequestContext; internal AsyncAcceptContext(HttpSysListener server) { Server = server; - AllocateNativeRequest(); + _preallocatedOverlapped = new(IOCallback, state: this, pinData: null); } internal HttpSysListener Server { get; } - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Redirecting to callback")] - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposed by callback")] + internal ValueTask AcceptAsync() + { + _mrvts.Reset(); + + AllocateNativeRequest(); + + uint statusCode = QueueBeginGetContext(); + if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && + statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) + { + // some other bad error, possible(?) return values are: + // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED + return ValueTask.FromException(new HttpSysException((int)statusCode)); + } + + return new ValueTask(this, _mrvts.Version); + } + private static void IOCompleted(AsyncAcceptContext asyncContext, uint errorCode, uint numBytes) { bool complete = false; + try { if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA) { - asyncContext.TrySetException(new HttpSysException((int)errorCode)); - complete = true; + asyncContext._mrvts.SetException(new HttpSysException((int)errorCode)); + return; } - else - { - HttpSysListener server = asyncContext.Server; - if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) - { - // at this point we have received an unmanaged HTTP_REQUEST and memoryBlob - // points to it we need to hook up our authentication handling code here. - try - { - if (server.ValidateRequest(asyncContext._nativeRequestContext) && server.ValidateAuth(asyncContext._nativeRequestContext)) - { - RequestContext requestContext = new RequestContext(server, asyncContext._nativeRequestContext); - asyncContext.TrySetResult(requestContext); - complete = true; - } - } - catch (Exception) - { - server.SendError(asyncContext._nativeRequestContext.RequestId, StatusCodes.Status400BadRequest); - throw; - } - finally - { - // The request has been handed to the user, which means this code can't reuse the blob. Reset it here. - if (complete) - { - asyncContext._nativeRequestContext = null; - } - else - { - asyncContext.AllocateNativeRequest(size: asyncContext._nativeRequestContext.Size); - } - } - } - else - { - // (uint)backingBuffer.Length - AlignmentPadding - asyncContext.AllocateNativeRequest(numBytes, asyncContext._nativeRequestContext.RequestId); - } - // We need to issue a new request, either because auth failed, or because our buffer was too small the first time. - if (!complete) + HttpSysListener server = asyncContext.Server; + if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) + { + // at this point we have received an unmanaged HTTP_REQUEST and memoryBlob + // points to it we need to hook up our authentication handling code here. + try { - uint statusCode = asyncContext.QueueBeginGetContext(); - if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && - statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) + var nativeContext = asyncContext._nativeRequestContext; + + if (server.ValidateRequest(nativeContext) && server.ValidateAuth(nativeContext)) { - // someother bad error, possible(?) return values are: - // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED - asyncContext.TrySetException(new HttpSysException((int)statusCode)); + // It's important that we clear the native request context before we set the result + // we want to reuse this object for future accepts. + asyncContext._nativeRequestContext = null; + + var requestContext = new RequestContext(server, nativeContext); + asyncContext._mrvts.SetResult(requestContext); + complete = true; } } - if (!complete) + catch (Exception ex) { - return; + server.SendError(asyncContext._nativeRequestContext.RequestId, StatusCodes.Status400BadRequest); + asyncContext._mrvts.SetException(ex); + } + finally + { + if (!complete) + { + asyncContext.AllocateNativeRequest(size: asyncContext._nativeRequestContext.Size); + } } } - - if (complete) + else { - asyncContext.Dispose(); + // (uint)backingBuffer.Length - AlignmentPadding + asyncContext.AllocateNativeRequest(numBytes, asyncContext._nativeRequestContext.RequestId); + } + + // We need to issue a new request, either because auth failed, or because our buffer was too small the first time. + if (!complete) + { + uint statusCode = asyncContext.QueueBeginGetContext(); + + if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && + statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) + { + // someother bad error, possible(?) return values are: + // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED + asyncContext._mrvts.SetException(new HttpSysException((int)statusCode)); + } } } catch (Exception exception) { - // Logged by caller - asyncContext.TrySetException(exception); - asyncContext.Dispose(); + asyncContext._mrvts.SetException(exception); } } private static unsafe void IOWaitCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped) { - // take the ListenerAsyncResult object from the state var asyncResult = (AsyncAcceptContext)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped); IOCompleted(asyncResult, errorCode, numBytes); } - internal uint QueueBeginGetContext() + private uint QueueBeginGetContext() { uint statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS; bool retry; @@ -133,7 +147,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys _nativeRequestContext.NativeRequest, _nativeRequestContext.Size, &bytesTransferred, - _nativeRequestContext.NativeOverlapped); + _overlapped); if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER && _nativeRequestContext.RequestId != 0) { @@ -162,18 +176,20 @@ namespace Microsoft.AspNetCore.Server.HttpSys return statusCode; } - internal void AllocateNativeRequest(uint? size = null, ulong requestId = 0) + private void AllocateNativeRequest(uint? size = null, ulong requestId = 0) { _nativeRequestContext?.ReleasePins(); _nativeRequestContext?.Dispose(); - // We can't reuse overlapped objects var boundHandle = Server.RequestQueue.BoundHandle; - var nativeOverlapped = new SafeNativeOverlapped(boundHandle, - boundHandle.AllocateNativeOverlapped(IOCallback, this, pinData: null)); - // nativeRequest - _nativeRequestContext = new NativeRequestContext(nativeOverlapped, Server.MemoryPool, size, requestId); + if (_overlapped != null) + { + boundHandle.FreeNativeOverlapped(_overlapped); + } + + _nativeRequestContext = new NativeRequestContext(Server.MemoryPool, size, requestId); + _overlapped = boundHandle.AllocateNativeOverlapped(_preallocatedOverlapped); } public void Dispose() @@ -189,8 +205,32 @@ namespace Microsoft.AspNetCore.Server.HttpSys { _nativeRequestContext.ReleasePins(); _nativeRequestContext.Dispose(); + _nativeRequestContext = null; + + var boundHandle = Server.RequestQueue.BoundHandle; + + if (_overlapped != null) + { + boundHandle.FreeNativeOverlapped(_overlapped); + _overlapped = null; + } } } } + + public RequestContext GetResult(short token) + { + return _mrvts.GetResult(token); + } + + public ValueTaskSourceStatus GetStatus(short token) + { + return _mrvts.GetStatus(token); + } + + public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) + { + _mrvts.OnCompleted(continuation, state, token, flags); + } } } diff --git a/src/Servers/HttpSys/src/HttpSysListener.cs b/src/Servers/HttpSys/src/HttpSysListener.cs index 4f4694ff5ce..42304b19139 100644 --- a/src/Servers/HttpSys/src/HttpSysListener.cs +++ b/src/Servers/HttpSys/src/HttpSysListener.cs @@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys return; } - Logger.LogTrace(LoggerEventIds.ListenerStopping,"Stopping the listener."); + Logger.LogTrace(LoggerEventIds.ListenerStopping, "Stopping the listener."); // If this instance created the queue then remove the URL prefixes before shutting down. if (_requestQueue.Created) @@ -277,34 +277,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys /// /// Accept a request from the incoming request queue. /// - public Task AcceptAsync() + internal ValueTask AcceptAsync(AsyncAcceptContext acceptContext) { - AsyncAcceptContext acceptContext = null; - try - { - CheckDisposed(); - Debug.Assert(_state != State.Stopped, "Listener has been stopped."); - // prepare the ListenerAsyncResult object (this will have it's own - // event that the user can wait on for IO completion - which means we - // need to signal it when IO completes) - acceptContext = new AsyncAcceptContext(this); - uint statusCode = acceptContext.QueueBeginGetContext(); - if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && - statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) - { - // some other bad error, possible(?) return values are: - // ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED - acceptContext.Dispose(); - throw new HttpSysException((int)statusCode); - } - } - catch (Exception exception) - { - Logger.LogError(LoggerEventIds.AcceptError, exception, "AcceptAsync"); - throw; - } + CheckDisposed(); + Debug.Assert(_state != State.Stopped, "Listener has been stopped."); - return acceptContext.Task; + return acceptContext.AcceptAsync(); } internal unsafe bool ValidateRequest(NativeRequestContext requestMemory) diff --git a/src/Servers/HttpSys/src/MessagePump.cs b/src/Servers/HttpSys/src/MessagePump.cs index e251fee5536..91dc8429b50 100644 --- a/src/Servers/HttpSys/src/MessagePump.cs +++ b/src/Servers/HttpSys/src/MessagePump.cs @@ -178,6 +178,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys // The awaits will manage stack depth for us. private async Task ProcessRequestsWorker() { + // Allocate and accept context per loop and reuse it for all accepts + using var acceptContext = new AsyncAcceptContext(Listener); + int workerIndex = Interlocked.Increment(ref _acceptorCounts); while (!Stopping && workerIndex <= _maxAccepts) { @@ -185,7 +188,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys RequestContext requestContext; try { - requestContext = await Listener.AcceptAsync(); + requestContext = await Listener.AcceptAsync(acceptContext); // Assign the message pump to this request context requestContext.MessagePump = this; } diff --git a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs index afa2c7c2ff7..fa6ae2e0a99 100644 --- a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs +++ b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using Microsoft.AspNetCore.HttpSys.Internal; using static Microsoft.AspNetCore.HttpSys.Internal.HttpApiTypes; @@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys internal static extern uint HttpReceiveClientCertificate(SafeHandle requestQueueHandle, ulong connectionId, uint flags, byte* pSslClientCertInfo, uint sslClientCertInfoSize, uint* pBytesReceived, SafeNativeOverlapped pOverlapped); [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - internal static extern uint HttpReceiveHttpRequest(SafeHandle requestQueueHandle, ulong requestId, uint flags, HTTP_REQUEST* pRequestBuffer, uint requestBufferLength, uint* pBytesReturned, SafeNativeOverlapped pOverlapped); + internal static extern uint HttpReceiveHttpRequest(SafeHandle requestQueueHandle, ulong requestId, uint flags, HTTP_REQUEST* pRequestBuffer, uint requestBufferLength, uint* pBytesReturned, NativeOverlapped* pOverlapped); [DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)] internal static extern uint HttpSendHttpResponse(SafeHandle requestQueueHandle, ulong requestId, uint flags, HTTP_RESPONSE_V2* pHttpResponse, HTTP_CACHE_POLICY* pCachePolicy, uint* pBytesSent, IntPtr pReserved1, uint Reserved2, SafeNativeOverlapped pOverlapped, IntPtr pLogData); diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs index 2448b3427a0..9e9d5a1c1bd 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs @@ -113,7 +113,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys.Listener /// internal static async Task AcceptAsync(this HttpSysListener server, TimeSpan timeout) { - var acceptTask = server.AcceptAsync(); + var acceptContext = new AsyncAcceptContext(server); + var acceptTask = server.AcceptAsync(acceptContext).AsTask(); var completedTask = await Task.WhenAny(acceptTask, Task.Delay(timeout)); if (completedTask == acceptTask) diff --git a/src/Shared/HttpSys/NativeInterop/SafeNativeOverlapped.cs b/src/Shared/HttpSys/NativeInterop/SafeNativeOverlapped.cs index 01ba3c32f23..f0a8d36669c 100644 --- a/src/Shared/HttpSys/NativeInterop/SafeNativeOverlapped.cs +++ b/src/Shared/HttpSys/NativeInterop/SafeNativeOverlapped.cs @@ -29,21 +29,6 @@ namespace Microsoft.AspNetCore.HttpSys.Internal get { return handle == IntPtr.Zero; } } - public void ReinitializeNativeOverlapped() - { - IntPtr handleSnapshot = handle; - - if (handleSnapshot != IntPtr.Zero) - { - unsafe - { - ((NativeOverlapped*)handleSnapshot)->InternalHigh = IntPtr.Zero; - ((NativeOverlapped*)handleSnapshot)->InternalLow = IntPtr.Zero; - ((NativeOverlapped*)handleSnapshot)->EventHandle = IntPtr.Zero; - } - } - } - protected override bool ReleaseHandle() { IntPtr oldHandle = Interlocked.Exchange(ref handle, IntPtr.Zero); diff --git a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs index b9a75b4ba9c..add799bd466 100644 --- a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs +++ b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs @@ -25,15 +25,12 @@ namespace Microsoft.AspNetCore.HttpSys.Internal private IMemoryOwner _backingBuffer; private MemoryHandle _memoryHandle; private int _bufferAlignment; - private SafeNativeOverlapped _nativeOverlapped; private bool _permanentlyPinned; private bool _disposed; // To be used by HttpSys - internal NativeRequestContext(SafeNativeOverlapped nativeOverlapped, MemoryPool memoryPool, uint? bufferSize, ulong requestId) + internal NativeRequestContext(MemoryPool memoryPool, uint? bufferSize, ulong requestId) { - _nativeOverlapped = nativeOverlapped; - // TODO: // Apparently the HttpReceiveHttpRequest memory alignment requirements for non - ARM processors // are different than for ARM processors. We have seen 4 - byte - aligned buffers allocated on @@ -70,8 +67,6 @@ namespace Microsoft.AspNetCore.HttpSys.Internal _permanentlyPinned = true; } - internal SafeNativeOverlapped NativeOverlapped => _nativeOverlapped; - internal HttpApiTypes.HTTP_REQUEST* NativeRequest { get @@ -130,8 +125,6 @@ namespace Microsoft.AspNetCore.HttpSys.Internal _memoryHandle.Dispose(); _memoryHandle = default; _nativeRequest = null; - _nativeOverlapped?.Dispose(); - _nativeOverlapped = null; } public virtual void Dispose() @@ -140,7 +133,6 @@ namespace Microsoft.AspNetCore.HttpSys.Internal { _disposed = true; Debug.Assert(_nativeRequest == null, "RequestContextBase::Dispose()|Dispose() called before ReleasePins()."); - _nativeOverlapped?.Dispose(); _memoryHandle.Dispose(); _backingBuffer.Dispose(); }