Skip to content

Commit d868553

Browse files
Avoid repetitive GCHandles in IIS WebSocket operations (#48462)
1 parent 62c1334 commit d868553

8 files changed

+42
-42
lines changed

src/Servers/IIS/IIS/src/Core/IISHttpContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,8 @@ protected virtual void Dispose(bool disposing)
763763
localAbortCts?.Dispose();
764764

765765
disposedValue = true;
766+
767+
AsyncIO?.Dispose();
766768
}
767769
}
768770

src/Servers/IIS/IIS/src/Core/IO/AsyncIOEngine.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Microsoft.AspNetCore.Server.IIS.Core.IO;
88

9-
internal sealed partial class AsyncIOEngine : IAsyncIOEngine
9+
internal sealed partial class AsyncIOEngine : IAsyncIOEngine, IDisposable
1010
{
1111
private const ushort ResponseMaxChunks = 65533;
1212

@@ -244,4 +244,9 @@ private void ReturnOperation(AsyncFlushOperation operation)
244244
{
245245
Volatile.Write(ref _cachedAsyncFlushOperation, operation);
246246
}
247+
248+
public void Dispose()
249+
{
250+
_stopped = true;
251+
}
247252
}

src/Servers/IIS/IIS/src/Core/IO/AsyncIOOperation.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ protected virtual void ResetOperation()
134134
_continuation = null;
135135
}
136136

137+
public bool InUse() => _continuation is not null;
138+
137139
public readonly struct AsyncContinuation
138140
{
139141
public Action<object?> Continuation { get; }

src/Servers/IIS/IIS/src/Core/IO/IAsyncIOEngine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace Microsoft.AspNetCore.Server.IIS.Core.IO;
77

8-
internal interface IAsyncIOEngine
8+
internal interface IAsyncIOEngine : IDisposable
99
{
1010
ValueTask<int> ReadAsync(Memory<byte> memory);
1111
ValueTask<int> WriteAsync(ReadOnlySequence<byte> data);

src/Servers/IIS/IIS/src/Core/IO/WebSocketsAsyncIOEngine.Initialize.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ protected override void ResetOperation()
3636
base.ResetOperation();
3737

3838
_requestHandler = default;
39-
_engine.ReturnOperation(this);
4039
}
4140
}
4241
}

src/Servers/IIS/IIS/src/Core/IO/WebSocketsAsyncIOEngine.Read.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core.IO;
99

1010
internal partial class WebSocketsAsyncIOEngine
1111
{
12-
internal sealed class WebSocketReadOperation : AsyncIOOperation
12+
internal sealed class WebSocketReadOperation : AsyncIOOperation, IDisposable
1313
{
1414
[UnmanagedCallersOnly]
1515
public static NativeMethods.REQUEST_NOTIFICATION_STATUS ReadCallback(IntPtr httpContext, IntPtr completionInfo, IntPtr completionContext)
@@ -26,21 +26,21 @@ public static NativeMethods.REQUEST_NOTIFICATION_STATUS ReadCallback(IntPtr http
2626
}
2727

2828
private readonly WebSocketsAsyncIOEngine _engine;
29-
private GCHandle _thisHandle;
29+
private readonly GCHandle _thisHandle;
3030
private MemoryHandle _inputHandle;
3131
private NativeSafeHandle? _requestHandler;
3232
private Memory<byte> _memory;
3333

3434
public WebSocketReadOperation(WebSocketsAsyncIOEngine engine)
3535
{
36+
_thisHandle = GCHandle.Alloc(this);
3637
_engine = engine;
3738
}
3839

3940
protected override unsafe bool InvokeOperation(out int hr, out int bytes)
4041
{
4142
Debug.Assert(_requestHandler != null, "Must initialize first.");
4243

43-
_thisHandle = GCHandle.Alloc(this);
4444
_inputHandle = _memory.Pin();
4545

4646
hr = NativeMethods.HttpWebsocketsReadBytes(
@@ -70,16 +70,17 @@ protected override void ResetOperation()
7070
{
7171
base.ResetOperation();
7272

73-
_thisHandle.Free();
74-
7573
_memory = default;
7674
_inputHandle.Dispose();
7775
_inputHandle = default;
7876
_requestHandler = default;
79-
80-
_engine.ReturnOperation(this);
8177
}
8278

8379
protected override bool IsSuccessfulResult(int hr) => hr == NativeMethods.ERROR_HANDLE_EOF;
80+
81+
public void Dispose()
82+
{
83+
_thisHandle.Free();
84+
}
8485
}
8586
}

src/Servers/IIS/IIS/src/Core/IO/WebSocketsAsyncIOEngine.Write.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Server.IIS.Core.IO;
88

99
internal partial class WebSocketsAsyncIOEngine
1010
{
11-
internal sealed class WebSocketWriteOperation : AsyncWriteOperationBase
11+
internal sealed class WebSocketWriteOperation : AsyncWriteOperationBase, IDisposable
1212
{
1313
[UnmanagedCallersOnly]
1414
private static NativeMethods.REQUEST_NOTIFICATION_STATUS WriteCallback(IntPtr httpContext, IntPtr completionInfo, IntPtr completionContext)
@@ -24,26 +24,27 @@ private static NativeMethods.REQUEST_NOTIFICATION_STATUS WriteCallback(IntPtr ht
2424
}
2525

2626
private readonly WebSocketsAsyncIOEngine _engine;
27-
private GCHandle _thisHandle;
27+
private readonly GCHandle _thisHandle;
2828

2929
public WebSocketWriteOperation(WebSocketsAsyncIOEngine engine)
3030
{
31+
_thisHandle = GCHandle.Alloc(this);
3132
_engine = engine;
3233
}
3334

3435
protected override unsafe int WriteChunks(NativeSafeHandle requestHandler, int chunkCount, HttpApiTypes.HTTP_DATA_CHUNK* dataChunks, out bool completionExpected)
3536
{
36-
_thisHandle = GCHandle.Alloc(this);
3737
return NativeMethods.HttpWebsocketsWriteBytes(requestHandler, dataChunks, chunkCount, &WriteCallback, (IntPtr)_thisHandle, out completionExpected);
3838
}
3939

4040
protected override void ResetOperation()
4141
{
4242
base.ResetOperation();
43+
}
4344

45+
public void Dispose()
46+
{
4447
_thisHandle.Free();
45-
46-
_engine.ReturnOperation(this);
4748
}
4849
}
4950
}

src/Servers/IIS/IIS/src/Core/IO/WebSocketsAsyncIOEngine.cs

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Buffers;
5+
using System.Diagnostics;
56

67
namespace Microsoft.AspNetCore.Server.IIS.Core.IO;
78

@@ -15,11 +16,9 @@ internal sealed partial class WebSocketsAsyncIOEngine : IAsyncIOEngine
1516

1617
private AsyncInitializeOperation? _initializationFlush;
1718

18-
private WebSocketWriteOperation? _cachedWebSocketWriteOperation;
19+
private WebSocketWriteOperation? _webSocketWriteOperation;
1920

20-
private WebSocketReadOperation? _cachedWebSocketReadOperation;
21-
22-
private AsyncInitializeOperation? _cachedAsyncInitializeOperation;
21+
private WebSocketReadOperation? _webSocketReadOperation;
2322

2423
public WebSocketsAsyncIOEngine(IISHttpContext context, NativeSafeHandle handler)
2524
{
@@ -33,7 +32,10 @@ public ValueTask<int> ReadAsync(Memory<byte> memory)
3332
{
3433
ThrowIfNotInitialized();
3534

36-
var read = GetReadOperation();
35+
var read = _webSocketReadOperation ??= new WebSocketReadOperation(this);
36+
37+
Debug.Assert(!read.InUse());
38+
3739
read.Initialize(_handler, memory);
3840
read.Invoke();
3941
return new ValueTask<int>(read, 0);
@@ -46,7 +48,10 @@ public ValueTask<int> WriteAsync(ReadOnlySequence<byte> data)
4648
{
4749
ThrowIfNotInitialized();
4850

49-
var write = GetWriteOperation();
51+
var write = _webSocketWriteOperation ??= new WebSocketWriteOperation(this);
52+
53+
Debug.Assert(!write.InUse());
54+
5055
write.Initialize(_handler, data);
5156
write.Invoke();
5257
return new ValueTask<int>(write, 0);
@@ -64,7 +69,7 @@ public ValueTask FlushAsync(bool moreData)
6469

6570
NativeMethods.HttpEnableWebsockets(_handler);
6671

67-
var init = GetInitializeOperation();
72+
var init = new AsyncInitializeOperation(this);
6873
init.Initialize(_handler);
6974

7075
var continuation = init.Invoke();
@@ -119,24 +124,9 @@ public void Complete()
119124
}
120125
}
121126

122-
private WebSocketReadOperation GetReadOperation() =>
123-
Interlocked.Exchange(ref _cachedWebSocketReadOperation, null) ??
124-
new WebSocketReadOperation(this);
125-
126-
private WebSocketWriteOperation GetWriteOperation() =>
127-
Interlocked.Exchange(ref _cachedWebSocketWriteOperation, null) ??
128-
new WebSocketWriteOperation(this);
129-
130-
private AsyncInitializeOperation GetInitializeOperation() =>
131-
Interlocked.Exchange(ref _cachedAsyncInitializeOperation, null) ??
132-
new AsyncInitializeOperation(this);
133-
134-
private void ReturnOperation(AsyncInitializeOperation operation) =>
135-
Volatile.Write(ref _cachedAsyncInitializeOperation, operation);
136-
137-
private void ReturnOperation(WebSocketWriteOperation operation) =>
138-
Volatile.Write(ref _cachedWebSocketWriteOperation, operation);
139-
140-
private void ReturnOperation(WebSocketReadOperation operation) =>
141-
Volatile.Write(ref _cachedWebSocketReadOperation, operation);
127+
public void Dispose()
128+
{
129+
_webSocketWriteOperation?.Dispose();
130+
_webSocketReadOperation?.Dispose();
131+
}
142132
}

0 commit comments

Comments
 (0)