Jelajahi Sumber

Merge pull request #880 from akarnokd/SystemClockChangeCrashFix

4.x: Fix InvalidOp when enumerating the SystemClockChanged hashset
Oren Novotny 6 tahun lalu
induk
melakukan
962a3f7913

+ 1 - 1
Rx.NET/Source/src/System.Reactive/Concurrency/LocalScheduler.TimerQueue.cs

@@ -370,7 +370,7 @@ namespace System.Reactive.Concurrency
         /// </summary>
         /// <param name="args">Currently not used.</param>
         /// <param name="sender">Currently not used.</param>
-        internal void SystemClockChanged(object sender, SystemClockChangedEventArgs args)
+        internal virtual void SystemClockChanged(object sender, SystemClockChangedEventArgs args)
         {
             lock (StaticGate)
             {

+ 5 - 3
Rx.NET/Source/src/System.Reactive/Internal/SystemClock.cs

@@ -21,7 +21,7 @@ namespace System.Reactive.PlatformServices
     {
         private static readonly Lazy<ISystemClock> ServiceSystemClock = new Lazy<ISystemClock>(InitializeSystemClock);
         private static readonly Lazy<INotifySystemClockChanged> ServiceSystemClockChanged = new Lazy<INotifySystemClockChanged>(InitializeSystemClockChanged);
-        private static readonly HashSet<WeakReference<LocalScheduler>> SystemClockChanged = new HashSet<WeakReference<LocalScheduler>>();
+        internal static readonly HashSet<WeakReference<LocalScheduler>> SystemClockChanged = new HashSet<WeakReference<LocalScheduler>>();
         private static IDisposable _systemClockChangedHandlerCollector;
 
         private static int _refCount;
@@ -55,11 +55,13 @@ namespace System.Reactive.PlatformServices
             }
         }
 
-        private static void OnSystemClockChanged(object sender, SystemClockChangedEventArgs e)
+        internal static void OnSystemClockChanged(object sender, SystemClockChangedEventArgs e)
         {
             lock (SystemClockChanged)
             {
-                foreach (var entry in SystemClockChanged)
+                // create a defensive copy as the callbacks may change the hashset
+                var copySystemClockChanged = new List<WeakReference<LocalScheduler>>(SystemClockChanged);
+                foreach (var entry in copySystemClockChanged)
                 {
                     if (entry.TryGetTarget(out var scheduler))
                     {

+ 31 - 0
Rx.NET/Source/tests/Tests.System.Reactive/Tests/SystemClockTest.cs

@@ -905,6 +905,37 @@ namespace ReactiveTests.Tests
             Assert.Equal(0, scm.N);
         }
 
+        [Fact]
+        public void SystemClockChange_SignalNoInvalidOperationExceptionDueToRemove()
+        {
+            var local = new RemoveScheduler();
+            SystemClock.SystemClockChanged.Add(new WeakReference<LocalScheduler>(local));
+
+            SystemClock.OnSystemClockChanged(this, new SystemClockChangedEventArgs());
+        }
+
+        private class RemoveScheduler : LocalScheduler
+        {
+            public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
+            {
+                throw new NotImplementedException();
+            }
+
+            internal override void SystemClockChanged(object sender, SystemClockChangedEventArgs args)
+            {
+                var target = default(WeakReference<LocalScheduler>);
+                foreach (var entries in SystemClock.SystemClockChanged)
+                {
+                    if (entries.TryGetTarget(out var local) && local == this)
+                    {
+                        target = entries;
+                        break;
+                    }
+                }
+                SystemClock.SystemClockChanged.Remove(target);
+            }
+        }
+
         private class MyScheduler : LocalScheduler
         {
             internal List<ScheduledItem<TimeSpan>> _queue = new List<ScheduledItem<TimeSpan>>();