From a9a0b9cd5342dcdde899395a139281570b107823 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 12 Jan 2023 12:33:30 +0100 Subject: [PATCH 1/4] Add NetworkChange implementation using PeriodicTimer --- .../src/System.Net.NetworkInformation.csproj | 5 +- .../NetworkAddressChange.Android.cs | 248 ++++++++++++++++++ 2 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs diff --git a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj index a77945404a17c..5db70d9ed2cd4 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj +++ b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj @@ -176,6 +176,9 @@ + + + @@ -188,7 +191,7 @@ - + diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs new file mode 100644 index 0000000000000..bfd1b178ea208 --- /dev/null +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs @@ -0,0 +1,248 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.NetworkInformation +{ + public partial class NetworkChange + { + private static readonly TimeSpan s_timerInterval = TimeSpan.FromSeconds(2); + private static readonly object s_lockObj = new(); + + private static Task? s_loopTask; + private static CancellationTokenSource? s_cancellationTokenSource; + + private static IPAddress[]? s_lastIpAddresses; + + [UnsupportedOSPlatform("illumos")] + [UnsupportedOSPlatform("solaris")] + public static event NetworkAddressChangedEventHandler? NetworkAddressChanged + { + add + { + if (value != null) + { + lock (s_lockObj) + { + if (s_addressChangedSubscribers.Count == 0 && + s_availabilityChangedSubscribers.Count == 0) + { + CreateAndStartLoop(); + } + else + { + Debug.Assert(s_loopTask is not null); + } + + s_addressChangedSubscribers.TryAdd(value, ExecutionContext.Capture()); + } + } + } + remove + { + if (value != null) + { + lock (s_lockObj) + { + bool hadAddressChangedSubscribers = s_addressChangedSubscribers.Count != 0; + s_addressChangedSubscribers.Remove(value); + + if (hadAddressChangedSubscribers && s_addressChangedSubscribers.Count == 0 && + s_availabilityChangedSubscribers.Count == 0) + { + StopLoop(); + } + } + } + } + } + + [UnsupportedOSPlatform("illumos")] + [UnsupportedOSPlatform("solaris")] + public static event NetworkAvailabilityChangedEventHandler? NetworkAvailabilityChanged + { + add + { + if (value != null) + { + lock (s_lockObj) + { + if (s_addressChangedSubscribers.Count == 0 && + s_availabilityChangedSubscribers.Count == 0) + { + CreateAndStartLoop(); + } + else + { + Debug.Assert(s_loopTask is not null); + } + + s_availabilityChangedSubscribers.TryAdd(value, ExecutionContext.Capture()); + } + } + } + remove + { + if (value != null) + { + lock (s_lockObj) + { + bool hadSubscribers = s_addressChangedSubscribers.Count != 0 || + s_availabilityChangedSubscribers.Count != 0; + s_availabilityChangedSubscribers.Remove(value); + + if (hadSubscribers && s_addressChangedSubscribers.Count == 0 && + s_availabilityChangedSubscribers.Count == 0) + { + StopLoop(); + } + } + } + } + } + + private static void CreateAndStartLoop() + { + Debug.Assert(s_cancellationTokenSource is null); + Debug.Assert(s_loopTask is null); + + s_cancellationTokenSource = new CancellationTokenSource(); + s_loopTask = Task.Run(Loop); + } + + private static void StopLoop() + { + s_cancellationTokenSource?.Cancel(); + + s_loopTask = null; + s_cancellationTokenSource = null; + } + + private static async Task Loop() + { + using var timer = new PeriodicTimer(s_timerInterval); + var token = s_cancellationTokenSource?.Token ?? throw new InvalidOperationException(); + + try + { + while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false) && !token.IsCancellationRequested) + { + CheckIfAddressChanged(); + } + } + catch (OperationCanceledException) + { + } + } + + private static void CheckIfAddressChanged() + { + var newAddresses = GetIPAddresses(); + if (s_lastIpAddresses is IPAddress[] oldAddresses && AddressesChanged(oldAddresses, newAddresses)) + { + OnAddressChanged(); + } + + s_lastIpAddresses = newAddresses; + } + + private static IPAddress[] GetIPAddresses() + { + var addresses = new List(); + + var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (var networkInterface in networkInterfaces) + { + var properties = networkInterface.GetIPProperties(); + foreach (var addressInformation in properties.UnicastAddresses) + { + addresses.Add(addressInformation.Address); + } + } + + return addresses.ToArray(); + } + + private static bool AddressesChanged(IPAddress[] oldAddresses, IPAddress[] newAddresses) + { + if (oldAddresses.Length != newAddresses.Length) + { + return true; + } + + for (int i = 0; i < newAddresses.Length; i++) + { + if (Array.IndexOf(oldAddresses, newAddresses[i]) == -1) + { + return true; + } + } + + return false; + } + + private static void OnAddressChanged() + { + Dictionary? addressChangedSubscribers = null; + Dictionary? availabilityChangedSubscribers = null; + + lock (s_lockObj) + { + if (s_addressChangedSubscribers.Count > 0) + { + addressChangedSubscribers = new Dictionary(s_addressChangedSubscribers); + } + if (s_availabilityChangedSubscribers.Count > 0) + { + availabilityChangedSubscribers = new Dictionary(s_availabilityChangedSubscribers); + } + } + + if (addressChangedSubscribers != null) + { + foreach (KeyValuePair + subscriber in addressChangedSubscribers) + { + NetworkAddressChangedEventHandler handler = subscriber.Key; + ExecutionContext? ec = subscriber.Value; + + if (ec == null) // Flow suppressed + { + handler(null, EventArgs.Empty); + } + else + { + ExecutionContext.Run(ec, s_runAddressChangedHandler, handler); + } + } + } + + if (availabilityChangedSubscribers != null) + { + bool isAvailable = NetworkInterface.GetIsNetworkAvailable(); + NetworkAvailabilityEventArgs args = isAvailable ? s_availableEventArgs : s_notAvailableEventArgs; + ContextCallback callbackContext = isAvailable ? s_runHandlerAvailable : s_runHandlerNotAvailable; + foreach (KeyValuePair + subscriber in availabilityChangedSubscribers) + { + NetworkAvailabilityChangedEventHandler handler = subscriber.Key; + ExecutionContext? ec = subscriber.Value; + + if (ec == null) // Flow suppressed + { + handler(null, args); + } + else + { + ExecutionContext.Run(ec, callbackContext, handler); + } + } + } + } + } +} From de07830cd89028a754178167aa2b12ffd2c88890 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 17 Jan 2023 14:49:00 +0100 Subject: [PATCH 2/4] Code cleanup --- .../NetworkAddressChange.Android.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs index bfd1b178ea208..fd426cc723a62 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs @@ -16,7 +16,6 @@ public partial class NetworkChange private static Task? s_loopTask; private static CancellationTokenSource? s_cancellationTokenSource; - private static IPAddress[]? s_lastIpAddresses; [UnsupportedOSPlatform("illumos")] @@ -112,25 +111,29 @@ private static void CreateAndStartLoop() Debug.Assert(s_loopTask is null); s_cancellationTokenSource = new CancellationTokenSource(); - s_loopTask = Task.Run(Loop); + s_loopTask = Task.Run(() => PeriodicallyCheckIfAddressChanged(s_cancellationTokenSource.Token)); } private static void StopLoop() { - s_cancellationTokenSource?.Cancel(); + Debug.Assert(s_cancellationTokenSource is not null); + Debug.Assert(s_loopTask is not null); + + s_cancellationTokenSource.Cancel(); s_loopTask = null; s_cancellationTokenSource = null; + s_lastIpAddresses = null; } - private static async Task Loop() + private static async Task PeriodicallyCheckIfAddressChanged(CancellationToken cancellationToken) { using var timer = new PeriodicTimer(s_timerInterval); - var token = s_cancellationTokenSource?.Token ?? throw new InvalidOperationException(); try { - while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false) && !token.IsCancellationRequested) + while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false) + && !cancellationToken.IsCancellationRequested) { CheckIfAddressChanged(); } From 2f57db06cc13c5185a2e360d397a62a31ab49bfe Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 18 Jan 2023 22:27:04 +0100 Subject: [PATCH 3/4] Rename functions --- .../NetworkAddressChange.Android.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs index fd426cc723a62..3bca7060682f6 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs @@ -111,7 +111,7 @@ private static void CreateAndStartLoop() Debug.Assert(s_loopTask is null); s_cancellationTokenSource = new CancellationTokenSource(); - s_loopTask = Task.Run(() => PeriodicallyCheckIfAddressChanged(s_cancellationTokenSource.Token)); + s_loopTask = Task.Run(() => PeriodicallyCheckIfNetworkChanged(s_cancellationTokenSource.Token)); } private static void StopLoop() @@ -126,7 +126,7 @@ private static void StopLoop() s_lastIpAddresses = null; } - private static async Task PeriodicallyCheckIfAddressChanged(CancellationToken cancellationToken) + private static async Task PeriodicallyCheckIfNetworkChanged(CancellationToken cancellationToken) { using var timer = new PeriodicTimer(s_timerInterval); @@ -135,7 +135,7 @@ private static async Task PeriodicallyCheckIfAddressChanged(CancellationToken ca while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false) && !cancellationToken.IsCancellationRequested) { - CheckIfAddressChanged(); + CheckIfNetworkChanged(); } } catch (OperationCanceledException) @@ -143,12 +143,12 @@ private static async Task PeriodicallyCheckIfAddressChanged(CancellationToken ca } } - private static void CheckIfAddressChanged() + private static void CheckIfNetworkChanged() { var newAddresses = GetIPAddresses(); - if (s_lastIpAddresses is IPAddress[] oldAddresses && AddressesChanged(oldAddresses, newAddresses)) + if (s_lastIpAddresses is IPAddress[] oldAddresses && NetworkChanged(oldAddresses, newAddresses)) { - OnAddressChanged(); + OnNetworkChanged(); } s_lastIpAddresses = newAddresses; @@ -171,7 +171,7 @@ private static IPAddress[] GetIPAddresses() return addresses.ToArray(); } - private static bool AddressesChanged(IPAddress[] oldAddresses, IPAddress[] newAddresses) + private static bool NetworkChanged(IPAddress[] oldAddresses, IPAddress[] newAddresses) { if (oldAddresses.Length != newAddresses.Length) { @@ -189,7 +189,7 @@ private static bool AddressesChanged(IPAddress[] oldAddresses, IPAddress[] newAd return false; } - private static void OnAddressChanged() + private static void OnNetworkChanged() { Dictionary? addressChangedSubscribers = null; Dictionary? availabilityChangedSubscribers = null; From df565037b5817248117f5d11605e17adfababc87 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 18 Jan 2023 22:33:00 +0100 Subject: [PATCH 4/4] Update csproj --- .../src/System.Net.NetworkInformation.csproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj index 5db70d9ed2cd4..e8621abe02e56 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj +++ b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj @@ -153,8 +153,9 @@ - + + @@ -176,9 +177,6 @@ - - -