tdsharp
tdsharp copied to clipboard
ExecuteAsync thread blocked after first call
The proposed workaround in below issue ("wrapping method calls in await Task.Run(() => /* method call */") does not seem to work in every case. I'm facing the same issue using the latest release 1.8.21. As long as the API call is long running (GetChatHistory in my case), there is a deadlock happening. Class TDClient is being used in a AspNetCore Kestrel server through a Singleton service.
Originally posted by @xpfr in https://github.com/egramtel/tdsharp/issues/60#issuecomment-2118883138
Please provide a code example and explain more what exactly is being blocked. A stack trace of a "blocked" thread would help a lot.
Thank you for looking into this.
I'm using a SignalR Hub in a minimal API AspNetCore template. This being said, I tried to make a very simple code to reproduce outside of the Hub class.
First I have a derived class from TdClient that handles the connection to TdLib and has the method to process updates from TdLib. internal class MyTdClient: TdClient { private const string ApplicationVersion = "1.0.0"; ... UpdateReceived += async (_, update) => { await ProcessUpdates(update); }; ... private async Task ProcessUpdates(TdApi.Update update) }
This class MyTdCLient is within a NetCore singleton using Lazy<T> as per the below: internal sealed class ChatsManager { private static readonly Lazy<ChatsManager> lazy = new(() => new ChatsManager());
public static ChatsManager Instance => lazy.Value;
private ChatsManager() {}
private MyTdClient Client { get; set; } = new MyTdClient();
... }
Program.cs: ... builder.Services.AddSingleton(sp => ChatsManager.Instance); var app = builder.Build(); //Code added to test if bug exists outside Hub class var r = await ChatsManager.Instance.GetChatHistory(5716535488); //
GetChatsHistory method:
internal async Task<List<PlatformIndependentTelegramMessage>> GetChatHistory(long chatId, int limit = pageLength, int idFromExcl = 0) { List<PlatformIndependentTelegramMessage> res = []; long fromMessageId = idFromExcl; //0: to start with last message while (res.Count < limit) { Logger?.LogDebug($"GetChatHistory: history from id={fromMessageId}"); var r = await Client.GetChatHistoryAsync(chatId, fromMessageId, 0, pageLength, false); int nb = r?.TotalCount ?? 0; Logger?.LogTrace($"GetChatHistory: got {nb} message(s)"); ... }
Call to GetChatHistory never returns.
Latest logs I'm getting are: GetChatHistory: history from id=0 2024-05-20T17:44:49.8313715 trce: TelegramServer[0] GetChatHistory: got 1 message(s) 2024-05-20T17:44:49.8330289 dbug: TelegramServer[0] GetChatHistory: history from id=1037041664
From DbLib logs, the library seems to return data from above call:
[ 3][t 4][1716056250.960387229][Td.cpp:2933][#1][!Td][&td_requests] Receive request 13: getChatHistory { chat_id = 5716535488 from_message_id = 1037041664 offset = 0 limit = 50 only_local = false } .... [ 3][t 4][1716056251.070115089][Td.cpp:4075][#1][!Td][&td_requests] Sending result for request 13: messages { total_count = 50 messages = vector[50] { message { ...
I created a dump with "dotnet-dump collect". The analysis mentions that ThreadPool is blocked on a synchronous call:
Hope it helps.
That doesn't help much. Could you please upload the exact code?
Also, the stack trace of the blocked thread would help.
I coded a single Program.cs which allows to reproduce the issue (need to set TdLib parameters in a web configuration file) [<TargetFramework>net8.0</TargetFramework>]
Stack of blocked thread:
System.Private.CoreLib.dll!System.Threading.Monitor.Wait(object obj, int millisecondsTimeout) Inconnu System.Private.CoreLib.dll!System.Threading.ManualResetEventSlim.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Inconnu System.Private.CoreLib.dll!System.Threading.Tasks.Task.SpinThenBlockingWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Inconnu System.Private.CoreLib.dll!System.Threading.Tasks.Task.InternalWaitCore(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Inconnu System.Private.CoreLib.dll!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task, System.Threading.Tasks.ConfigureAwaitOptions options) Inconnu TelegramServer.dll!Program.<Main>(string[] args) Inconnu
Thread with TDLib (note: updates continue to be received):
[Transition Managé à Natif] TdLib.dll!TdLib.TdJsonClient.Receive(double timeout) Inconnu TdLib.dll!TdLib.Bindings.Receiver.ProcessEvents() Inconnu TdLib.dll!TdLib.Bindings.Receiver.Start.AnonymousMethod__14_0() Inconnu [Reprise de la méthode Async] System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.AsyncStateMachineBox<TdLib.Bindings.Receiver.<<Start>b__14_0>d>.ExecutionContextCallback(object s) Inconnu System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread threadPoolThread, System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Inconnu System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.AsyncStateMachineBox<TdLib.Bindings.Receiver.<<Start>b__14_0>d>.MoveNext(System.Threading.Thread threadPoolThread) Inconnu System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.AsyncStateMachineBox<TdLib.Bindings.Receiver.<<Start>b__14_0>d>.ExecuteFromThreadPool(System.Threading.Thread threadPoolThread) Inconnu System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() Inconnu System.Private.CoreLib.dll!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart() Inconnu
I've had a look at Receiver.Start method and I'm wondering if the 'await Task.Yield()' defeats the purpose of LongRunning task creation option. As await Task.Yield() will make the continuation on a thread of the ThreadPool. See Note in https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#avoid-using-taskrun-for-long-running-work-that-blocks-the-thread See also https://sergeyteplyakov.github.io/Blog/async/2019/05/21/The-Dangers-of-Task.Factory.StartNew.html
It could be. But then, how does it block everything?
Are you talking about the field _stopped in this piece of code? In Bindings.cs internal class Receiver : IDisposable { ... private readonly ManualResetEventSlim _stopped = new ManualResetEventSlim(false); ...}
This field _stopped is only used when instance is disposed.
Le mar. 28 mai 2024 à 13:09, Alexander @.***> a écrit :
@xpfr https://github.com/xpfr try to replace ManualResetEventSlim to SemaphoreSlim and SemaphoreSlim.WaitAsync()
— Reply to this email directly, view it on GitHub https://github.com/egramtel/tdsharp/issues/192#issuecomment-2134951122, or unsubscribe https://github.com/notifications/unsubscribe-auth/AG72XAZWCMXII7AUF7TF7XTZERQXTAVCNFSM6AAAAABH5TPIGOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMZUHE2TCMJSGI . You are receiving this because you were mentioned.Message ID: @.***>
I cloned this project and recompiled libraries TdLib + TdLib.Api. When I use these recompiled libraries with my project, the issue is not present anymore (!). Could it be a compilation issue with the published Nuget package?
In theory, everything is possible. In practice, not so much. You'd have to debug and figure out what really happens.
I cloned the projet with the intent to debug the deadlock but as it works, not sure what I can do to help. We may want to close this issue unfortunately.