// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Threading; using Xunit; public partial class TimerFiringTests { internal const int MaxPositiveTimeoutInMs = 30000; [Fact] public void Timer_Fires_After_DueTime_Ellapses() { AutoResetEvent are = new AutoResetEvent(false); using (var t = new Timer(new TimerCallback((object s) => { are.Set(); }), null, TimeSpan.FromMilliseconds(250), TimeSpan.FromMilliseconds(Timeout.Infinite) /* not relevant */)) { Assert.True(are.WaitOne(TimeSpan.FromMilliseconds(MaxPositiveTimeoutInMs))); } } [Fact] public void Timer_Fires_AndPassesStateThroughCallback() { AutoResetEvent are = new AutoResetEvent(false); object state = new object(); using (var t = new Timer(new TimerCallback((object s) => { Assert.Same(s, state); are.Set(); }), state, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(Timeout.Infinite) /* not relevant */)) { Assert.True(are.WaitOne(TimeSpan.FromMilliseconds(MaxPositiveTimeoutInMs))); } } [Fact] public void Timer_Fires_AndPassesNullStateThroughCallback() { AutoResetEvent are = new AutoResetEvent(false); using (var t = new Timer(new TimerCallback((object s) => { Assert.Null(s); are.Set(); }), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(Timeout.Infinite) /* not relevant */)) { Assert.True(are.WaitOne(TimeSpan.FromMilliseconds(MaxPositiveTimeoutInMs))); } } [Fact] public void Timer_Fires_After_DueTime_AndOn_Period() { int count = 0; AutoResetEvent are = new AutoResetEvent(false); using (var t = new Timer(new TimerCallback((object s) => { if (Interlocked.Increment(ref count) >= 2) { are.Set(); } }), null, TimeSpan.FromMilliseconds(250), TimeSpan.FromMilliseconds(50))) { Assert.True(are.WaitOne(TimeSpan.FromMilliseconds(MaxPositiveTimeoutInMs))); } } [Fact] public void Timer_FiresOnlyOnce_OnDueTime_With_InfinitePeriod() { int count = 0; AutoResetEvent are = new AutoResetEvent(false); using (var t = new Timer(new TimerCallback((object s) => { if (Interlocked.Increment(ref count) >= 2) { are.Set(); } }), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(Timeout.Infinite) /* not relevant */)) { Assert.False(are.WaitOne(TimeSpan.FromMilliseconds(250 /*enough for 2 fires + buffer*/))); } } [Fact] public void Timer_CanDisposeSelfInCallback() { Timer t = null; AutoResetEvent are = new AutoResetEvent(false); TimerCallback tc = new TimerCallback((object o) => { t.Dispose(); are.Set(); }); t = new Timer(tc, null, -1, -1); t.Change(1, -1); Assert.True(are.WaitOne(MaxPositiveTimeoutInMs)); GC.KeepAlive(t); } [Fact] public void Timer_CanBeDisposedMultipleTimes() { // There's nothing to validate besides that we don't throw an exception, so rely on xunit // to catch any exception that would be thrown and signal a test failure TimerCallback tc = new TimerCallback((object o) => { }); var t = new Timer(tc, null, 100, -1); for (int i = 0; i < 10; i++) t.Dispose(); } [Fact] public void NonRepeatingTimer_ThatHasAlreadyFired_CanChangeAndFireAgain() { AutoResetEvent are = new AutoResetEvent(false); TimerCallback tc = new TimerCallback((object o) => are.Set()); using (var t = new Timer(tc, null, 1, Timeout.Infinite)) { Assert.True(are.WaitOne(MaxPositiveTimeoutInMs), "Should have received first timer event"); Assert.False(are.WaitOne(500), "Should not have received a second timer event"); t.Change(10, Timeout.Infinite); Assert.True(are.WaitOne(MaxPositiveTimeoutInMs), "Should have received a second timer event after changing it"); } } [Fact] public void Running_Timer_CanBeFinalizedAndStopsFiring() { AutoResetEvent are = new AutoResetEvent(false); TimerCallback tc = new TimerCallback((object o) => are.Set()); var t = new Timer(tc, null, 1, 500); Assert.True(are.WaitOne(MaxPositiveTimeoutInMs), "Failed to get first timer fire"); t = null; // Remove our refence so the timer can be GC'd GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Assert.False(are.WaitOne(500), "Should not have received a timer fire after it was collected"); } [Fact] public void MultpleTimers_PeriodicTimerIsntBlockedByBlockedCallback() { int callbacks = 2; Barrier b = new Barrier(callbacks + 1); Timer t = null; t = new Timer(_ => { if (Interlocked.Decrement(ref callbacks) >= 0) { Assert.True(b.SignalAndWait(MaxPositiveTimeoutInMs)); } t.Dispose(); }, null, -1, -1); t.Change(1, 50); Assert.True(b.SignalAndWait(MaxPositiveTimeoutInMs)); GC.KeepAlive(t); } [Fact] public void ManyTimers_EachTimerDoesFire() { int maxTimers = 10000; CountdownEvent ce = new CountdownEvent(maxTimers); Timer[] timers = System.Linq.Enumerable.Range(0, maxTimers).Select(_ => new Timer(s => ce.Signal(), null, 100 /* enough time to wait on the are */, -1)).ToArray(); try { Assert.True(ce.Wait(MaxPositiveTimeoutInMs), String.Format("Not all timers fired, {0} left of {1}", ce.CurrentCount, maxTimers)); } finally { foreach (Timer t in timers) t.Dispose(); } } }