Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 0628dda

Browse files
Caesar1995karelz
authored andcommitted
Fix System.Net.NetworkInformation.NetworkChange deadlock (#26259)
User code can subscribe to the NetworkAvailabilityChanged event. If a Network Address changed event get triggered while the user code is unsubscribing from the NetworkAvailabilityChanged event, there will be a deadlock. The deadlock can happen because two threads are holding two different locks for AddressChangeListener and AvailabilityChangeListener, and they are waiting for each other to release its lock first. The fix is using a global lock to replace two locks, so that we don’t allow operations on AddressChangeListener and AvailabilityChangeListener happen at the same time. Without fixing this, users can frequently see the deadlock when they put their computer to sleep, re-awaken it, then call the code that unsubscribes from NetworkAvailabilityChanged event. This deadlock is only specific to Windows implementation. For Unix implementations (Linux & OSX), their current implementation is to use a global lock object to protect both NetworkAddressChangedEventHandler & NetworkAvailabilityChangedEventHandler. This issue was reported in internal bug 162830.
1 parent 4e6c3c6 commit 0628dda

File tree

1 file changed

+37
-21
lines changed

1 file changed

+37
-21
lines changed

src/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Windows.cs

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public class NetworkChange
1515
//introduced for supporting design-time loading of System.Windows.dll
1616
[Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", true)]
1717
public static void RegisterNetworkChange(NetworkChange nc) { }
18+
19+
private static readonly object s_globalLock = new object();
1820

1921
public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged
2022
{
@@ -42,7 +44,6 @@ public static event NetworkAddressChangedEventHandler NetworkAddressChanged
4244

4345
internal static class AvailabilityChangeListener
4446
{
45-
private static readonly object s_syncObject = new object();
4647
private static readonly Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext> s_availabilityCallerArray =
4748
new Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext>();
4849
private static readonly NetworkAddressChangedEventHandler s_addressChange = ChangedAddress;
@@ -56,36 +57,44 @@ private static void RunHandlerCallback(object state)
5657

5758
private static void ChangedAddress(object sender, EventArgs eventArgs)
5859
{
59-
lock (s_syncObject)
60+
Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext> copy = null;
61+
62+
lock (s_globalLock)
6063
{
6164
bool isAvailableNow = SystemNetworkInterface.InternalGetIsNetworkAvailable();
6265

66+
// If there is an Availability Change, need to execute user callbacks.
6367
if (isAvailableNow != s_isAvailable)
6468
{
6569
s_isAvailable = isAvailableNow;
6670

67-
var s_copy =
71+
copy =
6872
new Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext>(s_availabilityCallerArray);
73+
}
74+
}
6975

70-
foreach (var handler in s_copy.Keys)
76+
// Executing user callbacks if Availability Change event occured.
77+
if (copy != null)
78+
{
79+
foreach (var entry in copy)
80+
{
81+
NetworkAvailabilityChangedEventHandler handler = entry.Key;
82+
ExecutionContext context = entry.Value;
83+
if (context == null)
7184
{
72-
ExecutionContext context = s_copy[handler];
73-
if (context == null)
74-
{
75-
handler(null, new NetworkAvailabilityEventArgs(s_isAvailable));
76-
}
77-
else
78-
{
79-
ExecutionContext.Run(context, s_RunHandlerCallback, handler);
80-
}
85+
handler(null, new NetworkAvailabilityEventArgs(s_isAvailable));
86+
}
87+
else
88+
{
89+
ExecutionContext.Run(context, s_RunHandlerCallback, handler);
8190
}
8291
}
8392
}
8493
}
8594

8695
internal static void Start(NetworkAvailabilityChangedEventHandler caller)
8796
{
88-
lock (s_syncObject)
97+
lock (s_globalLock)
8998
{
9099
if (s_availabilityCallerArray.Count == 0)
91100
{
@@ -102,7 +111,7 @@ internal static void Start(NetworkAvailabilityChangedEventHandler caller)
102111

103112
internal static void Stop(NetworkAvailabilityChangedEventHandler caller)
104113
{
105-
lock (s_syncObject)
114+
lock (s_globalLock)
106115
{
107116
s_availabilityCallerArray.Remove(caller);
108117
if (s_availabilityCallerArray.Count == 0)
@@ -132,7 +141,9 @@ internal static unsafe class AddressChangeListener
132141
// Callback fired when an address change occurs.
133142
private static void AddressChangedCallback(object stateObject, bool signaled)
134143
{
135-
lock (s_callerArray)
144+
Dictionary<NetworkAddressChangedEventHandler, ExecutionContext> copy;
145+
146+
lock (s_globalLock)
136147
{
137148
// The listener was canceled, which would only happen if we aren't listening for more events.
138149
s_isPending = false;
@@ -145,7 +156,7 @@ private static void AddressChangedCallback(object stateObject, bool signaled)
145156
s_isListening = false;
146157

147158
// Need to copy the array so the callback can call start and stop
148-
var copy = new Dictionary<NetworkAddressChangedEventHandler, ExecutionContext>(s_callerArray);
159+
copy = new Dictionary<NetworkAddressChangedEventHandler, ExecutionContext>(s_callerArray);
149160

150161
try
151162
{
@@ -156,10 +167,15 @@ private static void AddressChangedCallback(object stateObject, bool signaled)
156167
{
157168
if (NetEventSource.IsEnabled) NetEventSource.Error(null, nie);
158169
}
170+
}
159171

160-
foreach (var handler in copy.Keys)
172+
// Release the lock before calling into user callback.
173+
if (copy.Count > 0)
174+
{
175+
foreach (var entry in copy)
161176
{
162-
ExecutionContext context = copy[handler];
177+
NetworkAddressChangedEventHandler handler = entry.Key;
178+
ExecutionContext context = entry.Value;
163179
if (context == null)
164180
{
165181
handler(null, EventArgs.Empty);
@@ -189,7 +205,7 @@ internal static void UnsafeStart(NetworkAddressChangedEventHandler caller)
189205

190206
private static void StartHelper(NetworkAddressChangedEventHandler caller, bool captureContext, StartIPOptions startIPOptions)
191207
{
192-
lock (s_callerArray)
208+
lock (s_globalLock)
193209
{
194210
// Setup changedEvent and native overlapped struct.
195211
if (s_ipv4Socket == null)
@@ -315,7 +331,7 @@ private static void StartHelper(NetworkAddressChangedEventHandler caller, bool c
315331

316332
internal static void Stop(NetworkAddressChangedEventHandler caller)
317333
{
318-
lock (s_callerArray)
334+
lock (s_globalLock)
319335
{
320336
s_callerArray.Remove(caller);
321337
if (s_callerArray.Count == 0 && s_isListening)

0 commit comments

Comments
 (0)