mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-27 00:07:58 +01:00
Improved threadsafety of Application.MainLoop.AddTimeout
This commit is contained in:
@@ -51,6 +51,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
internal SortedList<long, Timeout> timeouts = new SortedList<long, Timeout> ();
|
||||
object timeoutsLockToken = new object ();
|
||||
internal List<Func<bool>> idleHandlers = new List<Func<bool>> ();
|
||||
|
||||
/// <summary>
|
||||
@@ -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 <c>false</c> 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<long, Timeout> ();
|
||||
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<long, Timeout> ();
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user