diff --git a/Terminal.Gui/Core/MainLoop.cs b/Terminal.Gui/Core/MainLoop.cs index 3812212d4..4fcdf2e8d 100644 --- a/Terminal.Gui/Core/MainLoop.cs +++ b/Terminal.Gui/Core/MainLoop.cs @@ -51,6 +51,7 @@ namespace Terminal.Gui { } internal SortedList timeouts = new SortedList (); + object timeoutsLockToken = new object (); internal List> idleHandlers = new List> (); /// @@ -117,7 +118,7 @@ namespace Terminal.Gui { void AddTimeout (TimeSpan time, Timeout timeout) { - lock (timeouts) { + lock (timeoutsLockToken) { var k = (DateTime.UtcNow + time).Ticks; while (timeouts.ContainsKey (k)) { k = (DateTime.UtcNow + time).Ticks; @@ -159,7 +160,7 @@ namespace Terminal.Gui { /// This method also returns false if the timeout is not found. public bool RemoveTimeout (object token) { - lock (timeouts) { + lock (timeoutsLockToken) { var idx = timeouts.IndexOfValue (token as Timeout); if (idx == -1) return false; @@ -170,18 +171,20 @@ namespace Terminal.Gui { void RunTimers () { - long now = DateTime.UtcNow.Ticks; - var copy = timeouts; - timeouts = new SortedList (); - foreach (var t in copy) { - var k = t.Key; - var timeout = t.Value; - if (k < now) { - if (timeout.Callback (this)) - AddTimeout (timeout.Span, timeout); - } else { - lock (timeouts) { - timeouts.Add (k, timeout); + lock (timeoutsLockToken) { + long now = DateTime.UtcNow.Ticks; + + var copy = timeouts; + timeouts = new SortedList (); + foreach (var t in copy) { + var k = t.Key; + var timeout = t.Value; + if (k < now) { + if (timeout.Callback (this)) + AddTimeout (timeout.Span, timeout); + } else { + timeouts.Add (k, timeout); + } } } diff --git a/UnitTests/ApplicationTests.cs b/UnitTests/ApplicationTests.cs index 2ceceefbd..2b5e7a619 100644 --- a/UnitTests/ApplicationTests.cs +++ b/UnitTests/ApplicationTests.cs @@ -1286,5 +1286,54 @@ namespace Terminal.Gui.Core { var cultures = Application.GetSupportedCultures (); Assert.Equal (cultures.Count, Application.SupportedCultures.Count); } + + [Fact, AutoInitShutdown] + public void TestAddManyTimeouts () + { + int delegatesRun = 0; + int numberOfThreads = 100; + int numberOfTimeoutsPerThread = 100; + + + // start lots of threads + for (int i = 0; i < numberOfThreads; i++) { + + var myi = i; + + Task.Run (() => { + Task.Delay (100).Wait (); + + // each thread registers lots of 1s timeouts + for(int j=0;j< numberOfTimeoutsPerThread; j++) { + + Application.MainLoop.AddTimeout (TimeSpan.FromSeconds(1), (s) => { + + // each timeout delegate increments delegatesRun count by 1 every second + Interlocked.Increment (ref delegatesRun); + return true; + }); + } + + // if this is the first Thread created + if (myi == 0) { + + // let the timeouts run for a bit + Task.Delay (5000).Wait (); + + // then tell the application to quuit + Application.MainLoop.Invoke (() => Application.RequestStop ()); + } + }); + } + + // blocks here until the RequestStop is processed at the end of the test + Application.Run (); + + // undershoot a bit to be on the safe side. The 5000 ms wait allows the timeouts to run + // a lot but all those timeout delegates could end up going slowly on a slow machine perhaps + // so the final number of delegatesRun might vary by computer. So for this assert we say + // that it should have run at least 2 seconds worth of delegates + Assert.True (delegatesRun >= 100 * 100 * 2); + } } }