8
8
using System . Threading ;
9
9
using System . Threading . Tasks ;
10
10
using System . Diagnostics ;
11
+ using Microsoft . Win32 . SafeHandles ;
11
12
12
13
namespace System . Net
13
14
{
@@ -138,17 +139,14 @@ public static unsafe string GetHostName()
138
139
{
139
140
Interop . Winsock . EnsureInitialized ( ) ;
140
141
141
- GetAddrInfoExContext * context = GetAddrInfoExContext . AllocateContext ( ) ;
142
-
143
- GetAddrInfoExState state ;
142
+ GetAddrInfoExState ? state = null ;
144
143
try
145
144
{
146
- state = new GetAddrInfoExState ( context , hostName , justAddresses ) ;
147
- context ->QueryStateHandle = state . CreateHandle ( ) ;
145
+ state = new GetAddrInfoExState ( hostName , justAddresses ) ;
148
146
}
149
147
catch
150
148
{
151
- GetAddrInfoExContext . FreeContext ( context ) ;
149
+ state ? . Dispose ( ) ;
152
150
throw ;
153
151
}
154
152
@@ -158,6 +156,8 @@ public static unsafe string GetHostName()
158
156
hints . ai_flags = AddressInfoHints . AI_CANONNAME ;
159
157
}
160
158
159
+ GetAddrInfoExContext * context = state . Context ;
160
+
161
161
SocketError errorCode = ( SocketError ) Interop . Winsock . GetAddrInfoExW (
162
162
hostName , null , Interop . Winsock . NS_ALL , IntPtr . Zero , & hints , & context ->Result , IntPtr . Zero , & context ->Overlapped , & GetAddressInfoExCallback , & context ->CancelHandle ) ;
163
163
@@ -172,7 +172,7 @@ public static unsafe string GetHostName()
172
172
// and final result would be posted via overlapped IO.
173
173
// synchronous failure here may signal issue when GetAddrInfoExW does not work from
174
174
// impersonated context. Windows 8 and Server 2012 fail for same reason with different errorCode.
175
- GetAddrInfoExContext . FreeContext ( context ) ;
175
+ state . Dispose ( ) ;
176
176
return null ;
177
177
}
178
178
else
@@ -194,10 +194,10 @@ private static unsafe void GetAddressInfoExCallback(int error, int bytes, Native
194
194
195
195
private static unsafe void ProcessResult ( SocketError errorCode , GetAddrInfoExContext * context )
196
196
{
197
+ GetAddrInfoExState state = GetAddrInfoExState . FromHandleAndFree ( context ->QueryStateHandle ) ;
198
+
197
199
try
198
200
{
199
- GetAddrInfoExState state = GetAddrInfoExState . FromHandleAndFree ( context ->QueryStateHandle ) ;
200
-
201
201
CancellationToken cancellationToken = state . UnregisterAndGetCancellationToken ( ) ;
202
202
203
203
if ( errorCode == SocketError . Success )
@@ -222,7 +222,7 @@ private static unsafe void ProcessResult(SocketError errorCode, GetAddrInfoExCon
222
222
}
223
223
finally
224
224
{
225
- GetAddrInfoExContext . FreeContext ( context ) ;
225
+ state . Dispose ( ) ;
226
226
}
227
227
}
228
228
@@ -360,18 +360,21 @@ private static unsafe IPAddress CreateIPv6Address(ReadOnlySpan<byte> socketAddre
360
360
return new IPAddress ( address , scope ) ;
361
361
}
362
362
363
- private sealed unsafe class GetAddrInfoExState : IThreadPoolWorkItem
363
+ // GetAddrInfoExState is a SafeHandle that manages the lifetime of GetAddrInfoExContext*
364
+ // to make sure GetAddrInfoExCancel always takes a valid memory address regardless of the race
365
+ // between cancellation and completion callbacks.
366
+ private sealed unsafe class GetAddrInfoExState : SafeHandleZeroOrMinusOneIsInvalid , IThreadPoolWorkItem
364
367
{
365
- private GetAddrInfoExContext * _cancellationContext ;
366
368
private CancellationTokenRegistration _cancellationRegistration ;
367
369
368
370
private AsyncTaskMethodBuilder < IPHostEntry > IPHostEntryBuilder ;
369
371
private AsyncTaskMethodBuilder < IPAddress [ ] > IPAddressArrayBuilder ;
370
372
private object ? _result ;
373
+ private volatile bool _completed ;
371
374
372
- public GetAddrInfoExState ( GetAddrInfoExContext * context , string hostName , bool justAddresses )
375
+ public GetAddrInfoExState ( string hostName , bool justAddresses )
376
+ : base ( true )
373
377
{
374
- _cancellationContext = context ;
375
378
HostName = hostName ;
376
379
JustAddresses = justAddresses ;
377
380
if ( justAddresses )
@@ -384,6 +387,10 @@ public GetAddrInfoExState(GetAddrInfoExContext *context, string hostName, bool j
384
387
IPHostEntryBuilder = AsyncTaskMethodBuilder < IPHostEntry > . Create ( ) ;
385
388
_ = IPHostEntryBuilder . Task ; // force initialization
386
389
}
390
+
391
+ GetAddrInfoExContext * context = GetAddrInfoExContext . AllocateContext ( ) ;
392
+ context ->QueryStateHandle = CreateHandle ( ) ;
393
+ SetHandle ( ( IntPtr ) context ) ;
387
394
}
388
395
389
396
public string HostName { get ; }
@@ -392,52 +399,62 @@ public GetAddrInfoExState(GetAddrInfoExContext *context, string hostName, bool j
392
399
393
400
public Task Task => JustAddresses ? ( Task ) IPAddressArrayBuilder . Task : IPHostEntryBuilder . Task ;
394
401
402
+ internal GetAddrInfoExContext * Context => ( GetAddrInfoExContext * ) handle ;
403
+
395
404
public void RegisterForCancellation ( CancellationToken cancellationToken )
396
405
{
397
406
if ( ! cancellationToken . CanBeCanceled ) return ;
398
407
399
- lock ( this )
408
+ if ( _completed )
400
409
{
401
- if ( _cancellationContext == null )
410
+ // The operation completed before registration could be done.
411
+ return ;
412
+ }
413
+
414
+ _cancellationRegistration = cancellationToken . UnsafeRegister ( static o =>
415
+ {
416
+ var @this = ( GetAddrInfoExState ) o ! ;
417
+ if ( @this . _completed )
402
418
{
403
- // The operation completed before registration could be done.
419
+ // Escape early and avoid ObjectDisposedException in DangerousAddRef
404
420
return ;
405
421
}
406
422
407
- _cancellationRegistration = cancellationToken . UnsafeRegister ( o =>
423
+ bool needRelease = false ;
424
+ try
408
425
{
409
- var @this = ( GetAddrInfoExState ) o ! ;
410
- int cancelResult = 0 ;
426
+ @this . DangerousAddRef ( ref needRelease ) ;
411
427
412
- lock ( @this )
413
- {
414
- GetAddrInfoExContext * context = @this . _cancellationContext ;
415
-
416
- if ( context != null )
417
- {
418
- // An outstanding operation will be completed with WSA_E_CANCELLED, and GetAddrInfoExCancel will return NO_ERROR.
419
- // If this thread has lost the race between cancellation and completion, this will be a NOP
420
- // with GetAddrInfoExCancel returning WSA_INVALID_HANDLE.
421
- cancelResult = Interop . Winsock . GetAddrInfoExCancel ( & context ->CancelHandle ) ;
422
- }
423
- }
428
+ // If DangerousAddRef didn't throw ODE, the handle should contain a valid pointer.
429
+ GetAddrInfoExContext * context = @this . Context ;
424
430
425
- if ( cancelResult != 0 && cancelResult != Interop . Winsock . WSA_INVALID_HANDLE && NetEventSource . Log . IsEnabled ( ) )
431
+ // An outstanding operation will be completed with WSA_E_CANCELLED, and GetAddrInfoExCancel will return NO_ERROR.
432
+ // If this thread has lost the race between cancellation and completion, this will be a NOP
433
+ // with GetAddrInfoExCancel returning WSA_INVALID_HANDLE.
434
+ int cancelResult = Interop . Winsock . GetAddrInfoExCancel ( & context ->CancelHandle ) ;
435
+ if ( cancelResult != Interop . Winsock . WSA_INVALID_HANDLE && NetEventSource . Log . IsEnabled ( ) )
426
436
{
427
437
NetEventSource . Info ( @this , $ "GetAddrInfoExCancel returned error { cancelResult } ") ;
428
438
}
429
- } , this ) ;
430
- }
439
+ }
440
+ finally
441
+ {
442
+ if ( needRelease )
443
+ {
444
+ @this . DangerousRelease ( ) ;
445
+ }
446
+ }
447
+
448
+ } , this ) ;
431
449
}
432
450
433
451
public CancellationToken UnregisterAndGetCancellationToken ( )
434
452
{
435
- lock ( this )
436
- {
437
- _cancellationContext = null ;
438
- _cancellationRegistration . Unregister ( ) ;
439
- }
453
+ _completed = true ;
440
454
455
+ // We should not wait for pending cancellation callbacks with CTR.Dispose(),
456
+ // since we are in a completion routine and GetAddrInfoExCancel may get blocked until it's finished.
457
+ _cancellationRegistration . Unregister ( ) ;
441
458
return _cancellationRegistration . Token ;
442
459
}
443
460
@@ -479,15 +496,22 @@ void IThreadPoolWorkItem.Execute()
479
496
}
480
497
}
481
498
482
- public IntPtr CreateHandle ( ) => GCHandle . ToIntPtr ( GCHandle . Alloc ( this , GCHandleType . Normal ) ) ;
483
-
484
499
public static GetAddrInfoExState FromHandleAndFree ( IntPtr handle )
485
500
{
486
501
GCHandle gcHandle = GCHandle . FromIntPtr ( handle ) ;
487
502
var state = ( GetAddrInfoExState ) gcHandle . Target ! ;
488
503
gcHandle . Free ( ) ;
489
504
return state ;
490
505
}
506
+
507
+ protected override bool ReleaseHandle ( )
508
+ {
509
+ GetAddrInfoExContext . FreeContext ( Context ) ;
510
+
511
+ return true ;
512
+ }
513
+
514
+ private IntPtr CreateHandle ( ) => GCHandle . ToIntPtr ( GCHandle . Alloc ( this , GCHandleType . Normal ) ) ;
491
515
}
492
516
493
517
[ StructLayout ( LayoutKind . Sequential ) ]
@@ -498,21 +522,15 @@ private unsafe struct GetAddrInfoExContext
498
522
public IntPtr CancelHandle ;
499
523
public IntPtr QueryStateHandle ;
500
524
501
- public static GetAddrInfoExContext * AllocateContext ( )
502
- {
503
- var context = ( GetAddrInfoExContext * ) Marshal . AllocHGlobal ( sizeof ( GetAddrInfoExContext ) ) ;
504
- * context = default ;
505
- return context ;
506
- }
525
+ public static GetAddrInfoExContext * AllocateContext ( ) => ( GetAddrInfoExContext * ) NativeMemory . AllocZeroed ( ( nuint ) sizeof ( GetAddrInfoExContext ) ) ;
507
526
508
527
public static void FreeContext ( GetAddrInfoExContext * context )
509
528
{
510
529
if ( context ->Result != null )
511
530
{
512
531
Interop . Winsock . FreeAddrInfoExW ( context ->Result ) ;
513
532
}
514
-
515
- Marshal . FreeHGlobal ( ( IntPtr ) context ) ;
533
+ NativeMemory . Free ( context ) ;
516
534
}
517
535
}
518
536
}
0 commit comments