chkr1011 / CoAPnet

CoAPnet is a high performance .NET library for CoAP based communication. It provides a CoAP client and a CoAP server. It also has DTLS support out of the box.
MIT License
70 stars 15 forks source link

CoapClient.Dispose blocking for some reason #7

Open n-develop opened 3 years ago

n-develop commented 3 years ago

For some reason, that I wasn't able to find yet, the Dispose function of the client is somehow blocked / won't exit. It doesn't matter If I run the code in a unit test or a console application. I'm using a using statement, just like in the sample code. The code runs fine. I implemented a couple of functions to work with the Ikea Gateway.

But as soon as my code hits the end of the using statement, it will just run forever. I also tried the example code from the wiki and I have the same behavior. So that could serve as my example code for the issue. But if I could provide any more details that can help you fix that, please let me know.

I'm using a MacBook Pro with Big Sur 11.2.3 and .NET 5 (5.0.102).

chkr1011 commented 3 years ago

That sounds strange to me. Please try with netstandard because I assume the following line may be the problem: https://github.com/chkr1011/CoAPnet/blob/master/Source/CoAPnet/Transport/TcpCoapTransportLayer.cs#L35

The tcp client is not set and not closed with the ? operator. I assume that also .NET 5.0 must be added to the compiler pragma because it should use the same code path as netstandard I guess.

n-develop commented 3 years ago

I'm not sure about the netstandard part. The nuget package is available for .NetStandard 2.0 and .NET Framework 4.5.2. So it's compiled for these two targets, correct?

With .NET5, the netstandard target is used already. Would it help, if I post the stack at the end of the program?

Main Thread:

ManualResetEventSlim.Wait()
Task.SpinThenBlockingWait()
Task.InternalWaitCore()
TaskAwaiter.HandleNonSuccessAndDebuggerNotification()
TaskAwaiter.GetResult()
Program.<Main>()

Other threads:

SpinWait.SpinOnceCore()
SpinWait.SpinOnce()
SafeSocketHandle.CloseAsIs()
Socket.Dispose()
Socket.Dispose()
UdpTransport.Dispose()
UdpTransport.Close()
DtlsCoapTransportLayer.<ReceiveAsync>b__13_0()
CancellationToken.<>c.<.cctor>b__26_0()
CancellationTokenSource.CallbackNode.<>c.<ExecuteCallback>b__9_0()
ExecutionContext.RunInternal() [4]
CancellationTokenSource.CallbackNode.ExecuteCallback()
CancellationTokenSource.ExecuteCallbackHandlers()
CancellationTokenSource.NotifyCancellation()
CancellationTokenSource.Cancel()
CoapClient.Dispose()
async Class1.Method1()
AsyncTaskMethodBuilder<VoidTaskResult>.AsyncStateMachineBox<__Canon>.ExecutionContextCallback()
ExecutionContext.RunInternal() [3]
AsyncTaskMethodBuilder<VoidTaskResult>.AsyncStateMachineBox<Class1.<Method1>d__0>.MoveNext()
AsyncTaskMethodBuilder<VoidTaskResult>.AsyncStateMachineBox<__Canon>.MoveNext()
AwaitTaskContinuation.RunOrScheduleAction() [3]
Task.RunContinuations() [3]
Task.FinishContinuations() [3]
Task<__Canon>.TrySetResult() [3]
AsyncTaskMethodBuilder<__Canon>.SetExistingTaskResult() [3]
AsyncTaskMethodBuilder<CoapResponse>.SetResult()
CoapClient.<RequestAsync>d__12.MoveNext()
AsyncTaskMethodBuilder<CoapResponse>.AsyncStateMachineBox<CoapClient.<RequestAsync>d__12>.ExecutionContextCallback()
ExecutionContext.RunInternal() [2]
AsyncTaskMethodBuilder<CoapResponse>.AsyncStateMachineBox<CoapClient.<RequestAsync>d__12>.MoveNext()
AsyncTaskMethodBuilder<CoapResponse>.AsyncStateMachineBox<CoapClient.<RequestAsync>d__12>.MoveNext()
AwaitTaskContinuation.RunOrScheduleAction() [2]
Task.RunContinuations() [2]
Task.FinishContinuations() [2]
Task<__Canon>.TrySetResult() [2]
AsyncTaskMethodBuilder<__Canon>.SetExistingTaskResult() [2]
AsyncTaskMethodBuilder<CoapMessage>.SetResult() [2]
CoapClient.<RequestAsync>d__15.MoveNext()
AsyncTaskMethodBuilder<CoapMessage>.AsyncStateMachineBox<CoapClient.<RequestAsync>d__15>.ExecutionContextCallback()
ExecutionContext.RunInternal() [1]
AsyncTaskMethodBuilder<CoapMessage>.AsyncStateMachineBox<CoapClient.<RequestAsync>d__15>.MoveNext()
AsyncTaskMethodBuilder<CoapMessage>.AsyncStateMachineBox<CoapClient.<RequestAsync>d__15>.MoveNext()
AwaitTaskContinuation.RunOrScheduleAction() [1]
Task.RunContinuations() [1]
Task.FinishContinuations() [1]
Task<__Canon>.TrySetResult() [1]
AsyncTaskMethodBuilder<__Canon>.SetExistingTaskResult() [1]
AsyncTaskMethodBuilder<CoapMessage>.SetResult() [1]
CoapMessageAwaiter.<WaitOneAsync>d__4.MoveNext()
AsyncTaskMethodBuilder<CoapMessage>.AsyncStateMachineBox<CoapMessageAwaiter.<WaitOneAsync>d__4>.ExecutionContextCallback()
ExecutionContext.RunFromThreadPoolDispatchLoop()
AsyncTaskMethodBuilder<CoapMessage>.AsyncStateMachineBox<CoapMessageAwaiter.<WaitOneAsync>d__4>.MoveNext()
AsyncTaskMethodBuilder<CoapMessage>.AsyncStateMachineBox<CoapMessageAwaiter.<WaitOneAsync>d__4>.ExecuteFromThreadPool()
ThreadPoolWorkQueue.Dispatch()
_ThreadPoolWaitCallback.PerformWaitCallback()
[Native to Managed Transition]
async .()

SocketPal.SysReceive()
SocketPal.TryCompleteReceiveFrom()
SocketAsyncContext.ReceiveFrom()
SocketPal.ReceiveFrom()
Socket.ReceiveFrom()
UdpTransport.Receive()
DtlsRecordLayer.ReceiveRecord()
DtlsRecordLayer.Receive()
DtlsTransport.Receive()
DtlsCoapTransportLayer.ReceiveAsync()
CoapTransportLayerAdapter.<ReceiveAsync>d__5.MoveNext()
AsyncMethodBuilderCore.Start<CoAPnet.Transport.CoapTransportLayerAdapter.<ReceiveAsync>d__5>()
AsyncTaskMethodBuilder<int>.Start<CoAPnet.Transport.CoapTransportLayerAdapter.<ReceiveAsync>d__5>()
CoapTransportLayerAdapter.ReceiveAsync()
LowLevelCoapClient.<ReceiveAsync>d__9.MoveNext()
AsyncMethodBuilderCore.Start<CoAPnet.LowLevelClient.LowLevelCoapClient.<ReceiveAsync>d__9>()
AsyncTaskMethodBuilder<CoapMessage>.Start<CoAPnet.LowLevelClient.LowLevelCoapClient.<ReceiveAsync>d__9>()
LowLevelCoapClient.ReceiveAsync()
CoapClient.<ReceiveMessages>d__17.MoveNext()
AsyncMethodBuilderCore.Start<CoAPnet.Client.CoapClient.<ReceiveMessages>d__17>()
AsyncTaskMethodBuilder.Start<CoAPnet.Client.CoapClient.<ReceiveMessages>d__17>()
CoapClient.ReceiveMessages()
CoapClient.<ConnectAsync>b__11_0()
Task<Task>.InnerInvoke()
Task.<>c.<.cctor>b__277_0()
ExecutionContext.RunInternal() [2]
Task.ExecuteWithThreadLocal()
Task.ExecuteEntryUnsafe()
ThreadPoolTaskScheduler.<>c.<.cctor>b__10_0()
ThreadHelper.ThreadStart_Context()
ExecutionContext.RunInternal() [1]
ThreadHelper.ThreadStart()
[Native to Managed Transition]
chkr1011 commented 3 years ago

You maybe need to upgrade bouncycastle. I saw that newer versions got some DTLS fixes with infinite loops etc. https://www.bouncycastle.org/csharp/#RELEASENOTES189

n-develop commented 3 years ago

I updated the BouncyCastle package, but that did not fix it. I forked your project. Maybe I can take a look at the problem. When I Iook at the second stack trace, it looks like the call to "UdpTransport.Close()" that was initialized by the CancellationToken leads to some kind of lock.

SpinWait.SpinOnceCore()
SpinWait.SpinOnce()
SafeSocketHandle.CloseAsIs()
Socket.Dispose()
Socket.Dispose()
UdpTransport.Dispose()
UdpTransport.Close()    <-- 3) The CancellationToken has a registered callback to call Close() on the UdpTransport
DtlsCoapTransportLayer.<ReceiveAsync>b__13_0()
CancellationToken.<>c.<.cctor>b__26_0()
CancellationTokenSource.CallbackNode.<>c.<ExecuteCallback>b__9_0()
ExecutionContext.RunInternal() [4]
CancellationTokenSource.CallbackNode.ExecuteCallback()
CancellationTokenSource.ExecuteCallbackHandlers()
CancellationTokenSource.NotifyCancellation()
CancellationTokenSource.Cancel()   <--  2) ... and that call the cancel method
CoapClient.Dispose()   <-- 1) I think this is triggered by the end of the using statement
...
chkr1011 commented 3 years ago

But having a lock in these classes cannot be fixed in this library I guess. Could you please try to call socket.Shutdown() before disposing the socket? What happens when you completely remove the call to socket.Dispose().

n-develop commented 3 years ago

Shutdown throws an exception, that the socket isn't open. When I remove the _socket?.Dispose() in UdpTransport.cs, it seems to work fine.

chkr1011 commented 3 years ago

Maybe it is related to an issue like this one: https://github.com/dotnet/runtime/issues/42686

n-develop commented 3 years ago

That looks similar, indeed.

chkr1011 commented 2 years ago

@n-develop Is this issue not yet fixed with a new .NET version?