Skip to content

Commit 962a3f7

Browse files
author
Oren Novotny
authored
Merge pull request #880 from akarnokd/SystemClockChangeCrashFix
4.x: Fix InvalidOp when enumerating the SystemClockChanged hashset
2 parents 04d98ea + 7d6fbf4 commit 962a3f7

File tree

3 files changed

+37
-4
lines changed

3 files changed

+37
-4
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ private static void EvaluateLongTermQueue()
370370
/// </summary>
371371
/// <param name="args">Currently not used.</param>
372372
/// <param name="sender">Currently not used.</param>
373-
internal void SystemClockChanged(object sender, SystemClockChangedEventArgs args)
373+
internal virtual void SystemClockChanged(object sender, SystemClockChangedEventArgs args)
374374
{
375375
lock (StaticGate)
376376
{

Rx.NET/Source/src/System.Reactive/Internal/SystemClock.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static class SystemClock
2121
{
2222
private static readonly Lazy<ISystemClock> ServiceSystemClock = new Lazy<ISystemClock>(InitializeSystemClock);
2323
private static readonly Lazy<INotifySystemClockChanged> ServiceSystemClockChanged = new Lazy<INotifySystemClockChanged>(InitializeSystemClockChanged);
24-
private static readonly HashSet<WeakReference<LocalScheduler>> SystemClockChanged = new HashSet<WeakReference<LocalScheduler>>();
24+
internal static readonly HashSet<WeakReference<LocalScheduler>> SystemClockChanged = new HashSet<WeakReference<LocalScheduler>>();
2525
private static IDisposable _systemClockChangedHandlerCollector;
2626

2727
private static int _refCount;
@@ -55,11 +55,13 @@ public static void Release()
5555
}
5656
}
5757

58-
private static void OnSystemClockChanged(object sender, SystemClockChangedEventArgs e)
58+
internal static void OnSystemClockChanged(object sender, SystemClockChangedEventArgs e)
5959
{
6060
lock (SystemClockChanged)
6161
{
62-
foreach (var entry in SystemClockChanged)
62+
// create a defensive copy as the callbacks may change the hashset
63+
var copySystemClockChanged = new List<WeakReference<LocalScheduler>>(SystemClockChanged);
64+
foreach (var entry in copySystemClockChanged)
6365
{
6466
if (entry.TryGetTarget(out var scheduler))
6567
{

Rx.NET/Source/tests/Tests.System.Reactive/Tests/SystemClockTest.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,37 @@ private static void ClockChanged_RefCounting_Callback()
905905
Assert.Equal(0, scm.N);
906906
}
907907

908+
[Fact]
909+
public void SystemClockChange_SignalNoInvalidOperationExceptionDueToRemove()
910+
{
911+
var local = new RemoveScheduler();
912+
SystemClock.SystemClockChanged.Add(new WeakReference<LocalScheduler>(local));
913+
914+
SystemClock.OnSystemClockChanged(this, new SystemClockChangedEventArgs());
915+
}
916+
917+
private class RemoveScheduler : LocalScheduler
918+
{
919+
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
920+
{
921+
throw new NotImplementedException();
922+
}
923+
924+
internal override void SystemClockChanged(object sender, SystemClockChangedEventArgs args)
925+
{
926+
var target = default(WeakReference<LocalScheduler>);
927+
foreach (var entries in SystemClock.SystemClockChanged)
928+
{
929+
if (entries.TryGetTarget(out var local) && local == this)
930+
{
931+
target = entries;
932+
break;
933+
}
934+
}
935+
SystemClock.SystemClockChanged.Remove(target);
936+
}
937+
}
938+
908939
private class MyScheduler : LocalScheduler
909940
{
910941
internal List<ScheduledItem<TimeSpan>> _queue = new List<ScheduledItem<TimeSpan>>();

0 commit comments

Comments
 (0)