private static void OnStabilized(IntPtr context, IntPtr table) { Interop.IpHlpApi.FreeMibTable(table); TeredoHelper helper = (TeredoHelper)GCHandle.FromIntPtr(context).Target !; // Lock the TeredoHelper instance to ensure that only the first call to OnStabilized will get to call // RunCallback. This is the only place that TeredoHelpers get locked, as individual instances are not // exposed to higher layers, so there's no chance for deadlock. if (!helper._runCallbackCalled) { lock (helper) { if (!helper._runCallbackCalled) { helper._runCallbackCalled = true; ThreadPool.QueueUserWorkItem(o => { TeredoHelper helper = (TeredoHelper)o !; // We are intentionally not calling Dispose synchronously inside the OnStabilized callback. // According to MSDN, calling CancelMibChangeNotify2 inside the callback results into deadlock. helper.Dispose(); helper._callback.Invoke(helper._state); }, helper); } } } }
// Returns true if the address table is already stable. Otherwise, calls callback when it becomes stable. // 'Unsafe' because it does not flow ExecutionContext to the callback. public static unsafe bool UnsafeNotifyStableUnicastIpAddressTable(Action <object> callback, object state) { Debug.Assert(callback != null); TeredoHelper?helper = new TeredoHelper(callback, state); try { uint err = Interop.IpHlpApi.NotifyStableUnicastIpAddressTable(AddressFamily.Unspecified, out SafeFreeMibTable table, &OnStabilized, GCHandle.ToIntPtr(helper._gcHandle), out helper._cancelHandle); table.Dispose(); if (err == Interop.IpHlpApi.ERROR_IO_PENDING) { Debug.Assert(helper._cancelHandle != null && !helper._cancelHandle.IsInvalid); // Suppress synchronous Dispose. Dispose will be called asynchronously by the callback. helper = null; return(false); } if (err != Interop.IpHlpApi.ERROR_SUCCESS) { throw new Win32Exception((int)err); } return(true); } finally { helper?.Dispose(); } }