mirror of
https://github.com/FabInfra/capnproto-dotnetcore_Runtime.git
synced 2025-03-12 23:01:44 +01:00
fixed deadlock + blocked thread detection
This commit is contained in:
parent
3252ef97f9
commit
8b88b5c5dd
@ -1,4 +1,5 @@
|
|||||||
using Capnp.Rpc;
|
using Capnp.Rpc;
|
||||||
|
using Capnp.Util;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -11,7 +12,7 @@ using System.Threading.Tasks.Dataflow;
|
|||||||
namespace Capnp.Net.Runtime.Tests
|
namespace Capnp.Net.Runtime.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class General
|
public class General: TestBase
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void AwaitOrderTest()
|
public void AwaitOrderTest()
|
||||||
@ -92,5 +93,115 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
Assert.IsTrue(t.IsCompleted);
|
Assert.IsTrue(t.IsCompleted);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SafeJoinCompletedThread()
|
||||||
|
{
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
thread.Start();
|
||||||
|
thread.SafeJoin(null, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SafeJoinBusyThread()
|
||||||
|
{
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (true) ;
|
||||||
|
}
|
||||||
|
catch (ThreadInterruptedException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Interrupted");
|
||||||
|
}
|
||||||
|
catch (ThreadAbortException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Aborted");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.Start();
|
||||||
|
thread.SafeJoin(null, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SafeJoinSleepingThread()
|
||||||
|
{
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.Sleep(Timeout.Infinite);
|
||||||
|
}
|
||||||
|
catch (ThreadInterruptedException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Interrupted");
|
||||||
|
}
|
||||||
|
catch (ThreadAbortException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Aborted");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thread.Start();
|
||||||
|
thread.SafeJoin(null, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SafeJoinDeadlockedThread()
|
||||||
|
{
|
||||||
|
var lk = new object();
|
||||||
|
|
||||||
|
lock (lk)
|
||||||
|
{
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (lk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ThreadInterruptedException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Interrupted");
|
||||||
|
}
|
||||||
|
catch (ThreadAbortException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Aborted");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thread.Start();
|
||||||
|
thread.SafeJoin(null, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SafeJoinDefensiveThread()
|
||||||
|
{
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
for (; ; )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.Sleep(Timeout.Infinite);
|
||||||
|
}
|
||||||
|
catch (ThreadInterruptedException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Interrupted");
|
||||||
|
}
|
||||||
|
catch (ThreadAbortException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.Start();
|
||||||
|
thread.SafeJoin(null, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void EagerRace2()
|
public void AwaitNoDeadlock()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 100; i++)
|
for (int i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
|
3
Capnp.Net.Runtime/Assembly.cs
Normal file
3
Capnp.Net.Runtime/Assembly.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Capnp.Net.Runtime.Tests")]
|
@ -1,4 +1,5 @@
|
|||||||
using Capnp.FrameTracing;
|
using Capnp.FrameTracing;
|
||||||
|
using Capnp.Util;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -92,6 +93,10 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
_pump.Run();
|
_pump.Run();
|
||||||
}
|
}
|
||||||
|
catch (ThreadInterruptedException)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{Thread.CurrentThread.Name} interrupted at {Environment.StackTrace}");
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
State = ConnectionState.Down;
|
State = ConnectionState.Down;
|
||||||
@ -186,10 +191,7 @@ namespace Capnp.Rpc
|
|||||||
Logger.LogError(e, "Failure disposing client");
|
Logger.LogError(e, "Failure disposing client");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pumpThread != null && !_pumpThread.Join(500))
|
_pumpThread?.SafeJoin(Logger);
|
||||||
{
|
|
||||||
Logger.LogError("Unable to join pump thread within timeout");
|
|
||||||
}
|
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Capnp.FrameTracing;
|
using Capnp.FrameTracing;
|
||||||
|
using Capnp.Util;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -60,6 +61,8 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
class Connection: IConnection
|
class Connection: IConnection
|
||||||
{
|
{
|
||||||
|
ILogger Logger { get; } = Logging.CreateLogger<Connection>();
|
||||||
|
|
||||||
readonly List<IFrameTracer> _tracers = new List<IFrameTracer>();
|
readonly List<IFrameTracer> _tracers = new List<IFrameTracer>();
|
||||||
readonly TcpRpcServer _server;
|
readonly TcpRpcServer _server;
|
||||||
Stream _stream;
|
Stream _stream;
|
||||||
@ -95,6 +98,10 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
Pump.Run();
|
Pump.Run();
|
||||||
}
|
}
|
||||||
|
catch (ThreadInterruptedException)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{Thread.CurrentThread.Name} interrupted at {Environment.StackTrace}");
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
OutboundEp.Dismiss();
|
OutboundEp.Dismiss();
|
||||||
@ -199,6 +206,10 @@ namespace Capnp.Rpc
|
|||||||
// Listener was stopped. Maybe a little bit rude, but this is
|
// Listener was stopped. Maybe a little bit rude, but this is
|
||||||
// our way of shutting down the acceptor thread.
|
// our way of shutting down the acceptor thread.
|
||||||
}
|
}
|
||||||
|
catch (ThreadInterruptedException)
|
||||||
|
{
|
||||||
|
Logger.LogError($"{Thread.CurrentThread.Name} interrupted at {Environment.StackTrace}");
|
||||||
|
}
|
||||||
catch (System.Exception exception)
|
catch (System.Exception exception)
|
||||||
{
|
{
|
||||||
// Any other exception might be due to some other problem.
|
// Any other exception might be due to some other problem.
|
||||||
@ -227,7 +238,7 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
connection.Client.Dispose();
|
connection.Client.Dispose();
|
||||||
connection.Pump?.Dispose();
|
connection.Pump?.Dispose();
|
||||||
connection.PumpRunner?.Join(5000);
|
connection.PumpRunner?.SafeJoin(Logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
_rpcEngine.BootstrapCap = null;
|
_rpcEngine.BootstrapCap = null;
|
||||||
|
@ -7,17 +7,21 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Capnp.Util
|
namespace Capnp.Util
|
||||||
{
|
{
|
||||||
public class StrictlyOrderedAwaitTask<T>: INotifyCompletion
|
internal class StrictlyOrderedAwaitTask<T>: INotifyCompletion
|
||||||
{
|
{
|
||||||
static readonly Action Capsule = () => throw new InvalidProgramException("Not invocable");
|
class Cover { }
|
||||||
|
class Seal { }
|
||||||
|
|
||||||
|
static readonly Cover s_cover = new Cover();
|
||||||
|
static readonly Seal s_seal = new Seal();
|
||||||
|
|
||||||
readonly Task<T> _awaitedTask;
|
readonly Task<T> _awaitedTask;
|
||||||
Action? _state;
|
object? _state;
|
||||||
|
|
||||||
public StrictlyOrderedAwaitTask(Task<T> awaitedTask)
|
public StrictlyOrderedAwaitTask(Task<T> awaitedTask)
|
||||||
{
|
{
|
||||||
_awaitedTask = awaitedTask;
|
_awaitedTask = awaitedTask;
|
||||||
AwaitInternal();
|
_state = s_cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StrictlyOrderedAwaitTask<T> GetAwaiter()
|
public StrictlyOrderedAwaitTask<T> GetAwaiter()
|
||||||
@ -41,41 +45,54 @@ namespace Capnp.Util
|
|||||||
Action? continuations;
|
Action? continuations;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
continuations = Interlocked.Exchange(ref _state, null);
|
continuations = (Action?)Interlocked.Exchange(ref _state, null);
|
||||||
continuations?.Invoke();
|
continuations?.Invoke();
|
||||||
|
|
||||||
} while (continuations != null);
|
} while (continuations != null);
|
||||||
|
|
||||||
return Interlocked.CompareExchange(ref _state, Capsule, null) == null;
|
return Interlocked.CompareExchange(ref _state, s_seal, null) == null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnCompleted(Action continuation)
|
public void OnCompleted(Action continuation)
|
||||||
{
|
{
|
||||||
|
bool first = false;
|
||||||
|
|
||||||
SpinWait.SpinUntil(() => {
|
SpinWait.SpinUntil(() => {
|
||||||
Action? cur, next;
|
object? cur, next;
|
||||||
cur = Volatile.Read(ref _state);
|
cur = Volatile.Read(ref _state);
|
||||||
|
first = false;
|
||||||
switch (cur)
|
switch (cur)
|
||||||
{
|
{
|
||||||
|
case Cover cover:
|
||||||
|
next = continuation;
|
||||||
|
first = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case null:
|
case null:
|
||||||
next = continuation;
|
next = continuation;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Action capsule when capsule == Capsule:
|
|
||||||
continuation();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case Action action:
|
case Action action:
|
||||||
next = action + continuation;
|
next = action + continuation;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
continuation();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Interlocked.CompareExchange(ref _state, next, cur) == cur;
|
return Interlocked.CompareExchange(ref _state, next, cur) == cur;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
AwaitInternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsCompleted => _awaitedTask.IsCompleted && _state == Capsule;
|
public bool IsCompleted => _awaitedTask.IsCompleted && _state == s_seal;
|
||||||
|
|
||||||
public T GetResult() => _awaitedTask.GetAwaiter().GetResult();
|
public T GetResult() => _awaitedTask.GetAwaiter().GetResult();
|
||||||
|
|
||||||
@ -84,7 +101,7 @@ namespace Capnp.Util
|
|||||||
public Task<T> WrappedTask => _awaitedTask;
|
public Task<T> WrappedTask => _awaitedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class StrictlyOrderedTaskExtensions
|
internal static class StrictlyOrderedTaskExtensions
|
||||||
{
|
{
|
||||||
public static StrictlyOrderedAwaitTask<T> EnforceAwaitOrder<T>(this Task<T> task)
|
public static StrictlyOrderedAwaitTask<T> EnforceAwaitOrder<T>(this Task<T> task)
|
||||||
{
|
{
|
||||||
|
49
Capnp.Net.Runtime/Util/ThreadExtensions.cs
Normal file
49
Capnp.Net.Runtime/Util/ThreadExtensions.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Capnp.Util
|
||||||
|
{
|
||||||
|
internal static class ThreadExtensions
|
||||||
|
{
|
||||||
|
class ThreadExtensionsLoggingContext
|
||||||
|
{
|
||||||
|
public ILogger Logger { get; } = Logging.CreateLogger<ThreadExtensionsLoggingContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Lazy<ThreadExtensionsLoggingContext> LoggingContext = new Lazy<ThreadExtensionsLoggingContext>(
|
||||||
|
() => new ThreadExtensionsLoggingContext(),
|
||||||
|
LazyThreadSafetyMode.PublicationOnly);
|
||||||
|
|
||||||
|
public static void SafeJoin(this Thread thread, ILogger? logger = null, int timeout = 5000)
|
||||||
|
{
|
||||||
|
if (!thread.Join(timeout))
|
||||||
|
{
|
||||||
|
logger ??= LoggingContext.Value.Logger;
|
||||||
|
|
||||||
|
string name = thread.Name ?? thread.ManagedThreadId.ToString();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.LogError($"Unable to join thread {name}. Thread is in state {thread.ThreadState}.");
|
||||||
|
thread.Interrupt();
|
||||||
|
if (!thread.Join(timeout / 10))
|
||||||
|
{
|
||||||
|
logger.LogError($"Still unable to join thread {name} after Interrupt(). Thread is in state {thread.ThreadState}.");
|
||||||
|
thread.Abort();
|
||||||
|
if (thread.Join(timeout / 10))
|
||||||
|
{
|
||||||
|
logger.LogError($"Still unable to join thread {name} after Abort(). Thread is in state {thread.ThreadState}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user