2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System . Collections . Concurrent ;
5
+ using System . Collections . Generic ;
5
6
using System . Diagnostics ;
6
7
using System . Runtime . CompilerServices ;
7
8
using System . Runtime . InteropServices ;
@@ -74,14 +75,17 @@ private static SocketAsyncEngine[] CreateEngines()
74
75
return engines ;
75
76
}
76
77
78
+ /// <summary>
79
+ /// Each <see cref="SocketAsyncContext"/> is assigned an index into this table while registered with a <see cref="SocketAsyncEngine"/>.
80
+ /// <para>The index is used as the <see cref="Interop.Sys.SocketEvent.Data"/> to quickly map events to <see cref="SocketAsyncContext"/>s.</para>
81
+ /// <para>It is also stored in <see cref="SocketAsyncContext.GlobalContextIndex"/> so that we can efficiently remove it when unregistering the socket.</para>
82
+ /// </summary>
83
+ private static SocketAsyncContext ? [ ] s_registeredContexts = [ ] ;
84
+ private static readonly Queue < int > s_registeredContextsFreeList = [ ] ;
85
+
77
86
private readonly IntPtr _port ;
78
87
private readonly Interop . Sys . SocketEvent * _buffer ;
79
88
80
- //
81
- // Maps handle values to SocketAsyncContext instances.
82
- //
83
- private readonly ConcurrentDictionary < IntPtr , SocketAsyncContextWrapper > _handleToContextMap = new ConcurrentDictionary < IntPtr , SocketAsyncContextWrapper > ( ) ;
84
-
85
89
//
86
90
// Queue of events generated by EventLoop() that would be processed by the thread pool
87
91
//
@@ -119,28 +123,54 @@ public static bool TryRegisterSocket(IntPtr socketHandle, SocketAsyncContext con
119
123
120
124
private bool TryRegisterCore ( IntPtr socketHandle , SocketAsyncContext context , out Interop . Error error )
121
125
{
122
- bool added = _handleToContextMap . TryAdd ( socketHandle , new SocketAsyncContextWrapper ( context ) ) ;
123
- if ( ! added )
126
+ Debug . Assert ( context . GlobalContextIndex == - 1 ) ;
127
+
128
+ lock ( s_registeredContextsFreeList )
124
129
{
125
- // Using public SafeSocketHandle(IntPtr) a user can add the same handle
126
- // from a different Socket instance.
127
- throw new InvalidOperationException ( SR . net_sockets_handle_already_used ) ;
130
+ if ( ! s_registeredContextsFreeList . TryDequeue ( out int index ) )
131
+ {
132
+ int previousLength = s_registeredContexts . Length ;
133
+ int newLength = Math . Max ( 4 , 2 * previousLength ) ;
134
+
135
+ Array . Resize ( ref s_registeredContexts , newLength ) ;
136
+
137
+ for ( int i = previousLength + 1 ; i < newLength ; i ++ )
138
+ {
139
+ s_registeredContextsFreeList . Enqueue ( i ) ;
140
+ }
141
+
142
+ index = previousLength ;
143
+ }
144
+
145
+ Debug . Assert ( s_registeredContexts [ index ] is null ) ;
146
+
147
+ s_registeredContexts [ index ] = context ;
148
+ context . GlobalContextIndex = index ;
128
149
}
129
150
130
151
error = Interop . Sys . TryChangeSocketEventRegistration ( _port , socketHandle , Interop . Sys . SocketEvents . None ,
131
- Interop . Sys . SocketEvents . Read | Interop . Sys . SocketEvents . Write , socketHandle ) ;
152
+ Interop . Sys . SocketEvents . Read | Interop . Sys . SocketEvents . Write , context . GlobalContextIndex ) ;
132
153
if ( error == Interop . Error . SUCCESS )
133
154
{
134
155
return true ;
135
156
}
136
157
137
- _handleToContextMap . TryRemove ( socketHandle , out _ ) ;
158
+ UnregisterSocket ( context ) ;
138
159
return false ;
139
160
}
140
161
141
- public void UnregisterSocket ( IntPtr socketHandle , SocketAsyncContext __ )
162
+ public static void UnregisterSocket ( SocketAsyncContext context )
142
163
{
143
- _handleToContextMap . TryRemove ( socketHandle , out _ ) ;
164
+ Debug . Assert ( context . GlobalContextIndex >= 0 ) ;
165
+ Debug . Assert ( ReferenceEquals ( s_registeredContexts [ context . GlobalContextIndex ] , context ) ) ;
166
+
167
+ lock ( s_registeredContextsFreeList )
168
+ {
169
+ s_registeredContexts [ context . GlobalContextIndex ] = null ;
170
+ s_registeredContextsFreeList . Enqueue ( context . GlobalContextIndex ) ;
171
+ }
172
+
173
+ context . GlobalContextIndex = - 1 ;
144
174
}
145
175
146
176
private SocketAsyncEngine ( )
@@ -324,13 +354,11 @@ private readonly struct SocketEventHandler
324
354
{
325
355
public Interop . Sys . SocketEvent * Buffer { get ; }
326
356
327
- private readonly ConcurrentDictionary < IntPtr , SocketAsyncContextWrapper > _handleToContextMap ;
328
357
private readonly ConcurrentQueue < SocketIOEvent > _eventQueue ;
329
358
330
359
public SocketEventHandler ( SocketAsyncEngine engine )
331
360
{
332
361
Buffer = engine . _buffer ;
333
- _handleToContextMap = engine . _handleToContextMap ;
334
362
_eventQueue = engine . _eventQueue ;
335
363
}
336
364
@@ -340,10 +368,15 @@ public bool HandleSocketEvents(int numEvents)
340
368
bool enqueuedEvent = false ;
341
369
foreach ( var socketEvent in new ReadOnlySpan < Interop . Sys . SocketEvent > ( Buffer , numEvents ) )
342
370
{
343
- if ( _handleToContextMap . TryGetValue ( socketEvent . Data , out SocketAsyncContextWrapper contextWrapper ) )
344
- {
345
- SocketAsyncContext context = contextWrapper . Context ;
371
+ Debug . Assert ( ( uint ) socketEvent . Data < ( uint ) s_registeredContexts . Length ) ;
346
372
373
+ // The context may be null if the socket was unregistered right before the event was processed.
374
+ // The slot in s_registeredContexts may have been reused by a different context, in which case the
375
+ // incorrect socket will notice that no information is available yet and harmlessly retry, waiting for new events.
376
+ SocketAsyncContext ? context = s_registeredContexts [ ( uint ) socketEvent . Data ] ;
377
+
378
+ if ( context is not null )
379
+ {
347
380
if ( context . PreferInlineCompletions )
348
381
{
349
382
context . HandleEventsInline ( socketEvent . Events ) ;
@@ -365,18 +398,6 @@ public bool HandleSocketEvents(int numEvents)
365
398
}
366
399
}
367
400
368
- // struct wrapper is used in order to improve the performance of the epoll thread hot path by up to 3% of some TechEmpower benchmarks
369
- // the goal is to have a dedicated generic instantiation and using:
370
- // System.Collections.Concurrent.ConcurrentDictionary`2[System.IntPtr,System.Net.Sockets.SocketAsyncContextWrapper]::TryGetValueInternal(!0,int32,!1&)
371
- // instead of:
372
- // System.Collections.Concurrent.ConcurrentDictionary`2[System.IntPtr,System.__Canon]::TryGetValueInternal(!0,int32,!1&)
373
- private readonly struct SocketAsyncContextWrapper
374
- {
375
- public SocketAsyncContextWrapper ( SocketAsyncContext context ) => Context = context ;
376
-
377
- internal SocketAsyncContext Context { get ; }
378
- }
379
-
380
401
private readonly struct SocketIOEvent
381
402
{
382
403
public SocketAsyncContext Context { get ; }
0 commit comments