4
4
using System . Collections . Generic ;
5
5
using System . ComponentModel ;
6
6
using System . Diagnostics ;
7
+ using System . Runtime . InteropServices ;
7
8
using System . Net . Sockets ;
8
9
using System . Threading ;
9
10
@@ -22,22 +23,15 @@ namespace System.Net.NetworkInformation
22
23
// CancelMibChangeNotify2 guarantees that, by the time it returns, all calls to the callback will be complete
23
24
// and that no new calls to the callback will be issued.
24
25
//
25
- // The major concerns of the class are: 1) making sure none of the managed objects needed to handle a native
26
- // callback are GC'd before the callback, and 2) making sure no native callbacks will try to call into an unloaded
27
- // AppDomain.
28
26
29
27
internal class TeredoHelper
30
28
{
31
- // Holds a list of all pending calls to NotifyStableUnicastIpAddressTable. Also used as a lock to protect its
32
- // contents.
33
- private static readonly List < TeredoHelper > s_pendingNotifications = new List < TeredoHelper > ( ) ;
34
29
private readonly Action < object > _callback ;
35
30
private readonly object _state ;
36
31
37
32
private bool _runCallbackCalled ;
38
33
39
- // We explicitly keep a copy of this to prevent it from getting GC'd.
40
- private readonly Interop . IpHlpApi . StableUnicastIpAddressTableDelegate _onStabilizedDelegate ;
34
+ private GCHandle _gcHandle ;
41
35
42
36
// Used to cancel notification after receiving the first callback, or when the AppDomain is going down.
43
37
private SafeCancelMibChangeNotify ? _cancelHandle ;
@@ -46,123 +40,85 @@ private TeredoHelper(Action<object> callback, object state)
46
40
{
47
41
_callback = callback ;
48
42
_state = state ;
49
- _onStabilizedDelegate = new Interop . IpHlpApi . StableUnicastIpAddressTableDelegate ( OnStabilized ) ;
50
- _runCallbackCalled = false ;
43
+
44
+ _gcHandle = GCHandle . Alloc ( this ) ;
51
45
}
52
46
53
47
// Returns true if the address table is already stable. Otherwise, calls callback when it becomes stable.
54
48
// 'Unsafe' because it does not flow ExecutionContext to the callback.
55
- public static bool UnsafeNotifyStableUnicastIpAddressTable ( Action < object > callback , object state )
49
+ public static unsafe bool UnsafeNotifyStableUnicastIpAddressTable ( Action < object > callback , object state )
56
50
{
57
- if ( callback == null )
58
- {
59
- NetEventSource . Fail ( null , "UnsafeNotifyStableUnicastIpAddressTable called without specifying callback!" ) ;
60
- }
51
+ Debug . Assert ( callback != null ) ;
61
52
62
- TeredoHelper helper = new TeredoHelper ( callback , state ) ;
53
+ TeredoHelper ? helper = new TeredoHelper ( callback , state ) ;
54
+ try
55
+ {
56
+ uint err = Interop . IpHlpApi . NotifyStableUnicastIpAddressTable ( AddressFamily . Unspecified ,
57
+ out SafeFreeMibTable table , & OnStabilized , GCHandle . ToIntPtr ( helper . _gcHandle ) , out helper . _cancelHandle ) ;
63
58
64
- uint err = Interop . IpHlpApi . ERROR_SUCCESS ;
65
- SafeFreeMibTable table ;
59
+ table . Dispose ( ) ;
66
60
67
- lock ( s_pendingNotifications )
68
- {
69
- // If OnAppDomainUnload gets the lock first, tell our caller that we'll finish async. Their AppDomain
70
- // is about to go down anyways. If we do, hold the lock until we've added helper to the
71
- // s_pendingNotifications list (if we're going to complete asynchronously).
72
- if ( Environment . HasShutdownStarted )
61
+ if ( err == Interop . IpHlpApi . ERROR_IO_PENDING )
73
62
{
63
+ Debug . Assert ( helper . _cancelHandle != null && ! helper . _cancelHandle . IsInvalid ) ;
64
+
65
+ // Suppress synchronous Dispose. Dispose will be called asynchronously by the callback.
66
+ helper = null ;
74
67
return false ;
75
68
}
76
69
77
- err = Interop . IpHlpApi . NotifyStableUnicastIpAddressTable ( AddressFamily . Unspecified ,
78
- out table , helper . _onStabilizedDelegate , IntPtr . Zero , out helper . _cancelHandle ) ;
79
-
80
- if ( table != null )
70
+ if ( err != Interop . IpHlpApi . ERROR_SUCCESS )
81
71
{
82
- table . Dispose ( ) ;
72
+ throw new Win32Exception ( ( int ) err ) ;
83
73
}
84
74
85
- if ( err == Interop . IpHlpApi . ERROR_IO_PENDING )
86
- {
87
- if ( helper . _cancelHandle . IsInvalid )
88
- {
89
- NetEventSource . Fail ( null , "CancelHandle invalid despite returning ERROR_IO_PENDING" ) ;
90
- }
91
-
92
- // Async completion: add us to the s_pendingNotifications list so we'll be canceled in the
93
- // event of an AppDomain unload.
94
- s_pendingNotifications . Add ( helper ) ;
95
- return false ;
96
- }
75
+ return true ;
97
76
}
98
-
99
- if ( err != Interop . IpHlpApi . ERROR_SUCCESS )
77
+ finally
100
78
{
101
- throw new Win32Exception ( ( int ) err ) ;
79
+ helper ? . Dispose ( ) ;
102
80
}
103
-
104
- return true ;
105
81
}
106
82
107
- private void RunCallback ( object ? o )
83
+ private void Dispose ( )
108
84
{
109
- if ( ! _runCallbackCalled )
110
- {
111
- NetEventSource . Fail ( null , "RunCallback called without setting runCallbackCalled!" ) ;
112
- }
113
-
114
- // If OnAppDomainUnload beats us to the lock, do nothing: the AppDomain is going down soon anyways.
115
- // Otherwise, wait until the call to CancelMibChangeNotify2 is done before giving it up.
116
- lock ( s_pendingNotifications )
117
- {
118
- if ( Environment . HasShutdownStarted )
119
- {
120
- return ;
121
- }
85
+ _cancelHandle ? . Dispose ( ) ;
122
86
123
- #if DEBUG
124
- bool successfullyRemoved = s_pendingNotifications . Remove ( this ) ;
125
- if ( ! successfullyRemoved )
126
- {
127
- NetEventSource . Fail ( null , "RunCallback for a TeredoHelper which is not in s_pendingNotifications!" ) ;
128
- }
129
- #else
130
- s_pendingNotifications . Remove ( this ) ;
131
- #endif
132
-
133
- if ( ( _cancelHandle == null || _cancelHandle . IsInvalid ) )
134
- {
135
- NetEventSource . Fail ( null , "Invalid cancelHandle in RunCallback" ) ;
136
- }
137
-
138
- _cancelHandle . Dispose ( ) ;
139
- }
140
-
141
- _callback . Invoke ( _state ) ;
87
+ if ( _gcHandle . IsAllocated )
88
+ _gcHandle . Free ( ) ;
142
89
}
143
90
144
91
// This callback gets run on a native worker thread, which we don't want to allow arbitrary user code to
145
92
// execute on (it will block AppDomain unload, for one). Free the MibTable and delegate (exactly once)
146
93
// to the managed ThreadPool for the rest of the processing.
147
- //
148
- // We can't use SafeHandle here for table because the marshaller doesn't support them in reverse p/invokes.
149
- // We won't get an AppDomain unload here anyways, because OnAppDomainUnloaded will block until all of these
150
- // callbacks are done.
151
- private void OnStabilized ( IntPtr context , IntPtr table )
94
+ [ UnmanagedCallersOnly ]
95
+ private static void OnStabilized ( IntPtr context , IntPtr table )
152
96
{
153
97
Interop . IpHlpApi . FreeMibTable ( table ) ;
154
98
99
+ TeredoHelper helper = ( TeredoHelper ) GCHandle . FromIntPtr ( context ) . Target ! ;
100
+
155
101
// Lock the TeredoHelper instance to ensure that only the first call to OnStabilized will get to call
156
102
// RunCallback. This is the only place that TeredoHelpers get locked, as individual instances are not
157
103
// exposed to higher layers, so there's no chance for deadlock.
158
- if ( ! _runCallbackCalled )
104
+ if ( ! helper . _runCallbackCalled )
159
105
{
160
- lock ( this )
106
+ lock ( helper )
161
107
{
162
- if ( ! _runCallbackCalled )
108
+ if ( ! helper . _runCallbackCalled )
163
109
{
164
- _runCallbackCalled = true ;
165
- ThreadPool . QueueUserWorkItem ( RunCallback , null ) ;
110
+ helper . _runCallbackCalled = true ;
111
+
112
+ ThreadPool . QueueUserWorkItem ( o =>
113
+ {
114
+ TeredoHelper helper = ( TeredoHelper ) o ! ;
115
+
116
+ // We are intentionally not calling Dispose synchronously inside the OnStabilized callback.
117
+ // According to MSDN, calling CancelMibChangeNotify2 inside the callback results into deadlock.
118
+ helper . Dispose ( ) ;
119
+
120
+ helper . _callback . Invoke ( helper . _state ) ;
121
+ } , helper ) ;
166
122
}
167
123
}
168
124
}
0 commit comments