Skip to content

TypingTimer is not thread-safe and causes deadlocks with message sending #764

@shunsaker-macu

Description

@shunsaker-macu

Version

What package version of the SDK are you using.

  • Nuget package version: Microsoft.Agents.Builder 1.5.69-beta
  • dll product version: 1.5.69-beta

Describe the bug

Under concurrency (multiple turns/responses processed at the same time), the TypingTimer can deadlock message sending. Calls that ultimately send a "message" activity (or streaming updates) can hang indefinitely inside TypingTimer.StopTimerWhenSendMessageActivityHandlerAsync, blocked on _send.WaitOne().

The implementation uses a static AutoResetEvent for synchronization (private static AutoResetEvent _send;) and reassigns it each time TypingTimer.Start() is called (_send = new AutoResetEvent(false);). When two turns overlap, one timer can overwrite _send while another turn is blocked waiting on the previous event instance. If the awaited instance is never signaled, WaitOne() blocks forever with no cancellation or timeout.

This manifests as:

  • The agent continues to emit typing indicators forever
  • A subsequent send (for example StreamingResponse.QueueInformativeUpdateAsync("Thinking...") or a normal message send) never completes
  • No exception is thrown; the request/turn can hang forever

Stack trace (hours after QueueInformativeUpdateAsync call)

System.Private.CoreLib.dll!System.Threading.WaitHandle.WaitOneNoCheck(int millisecondsTimeout)
Microsoft.Agents.Builder.dll!Microsoft.Agents.Builder.App.TypingTimer.StopTimerWhenSendMessageActivityHandlerAsync(...) Line 99
Microsoft.Agents.Builder.dll!Microsoft.Agents.Builder.TurnContext.SendActivitiesAsync.__SendActivitiesThroughCallbackPipeline|0(...) Line 163
Microsoft.Agents.Builder.dll!Microsoft.Agents.Builder.TurnContext.SendActivitiesAsync(...) Line 140
Microsoft.Agents.Builder.dll!Microsoft.Agents.Builder.TurnContext.SendActivityAsync(...) Line 114
Microsoft.Agents.Builder.dll!Microsoft.Agents.Builder.StreamingResponse.SendActivityAsync(...) Line 472
Microsoft.Agents.Builder.dll!Microsoft.Agents.Builder.StreamingResponse.QueueInformativeUpdateAsync(...) Line 187

To Reproduce

Goal: Trigger overlapping turns that both auto-start the TypingTimer, then attempt to send a message while the stop-typing handler blocks on a lost _send signal.

Channel / client

  • Microsoft Teams
  • Observed with streaming enabled and also in non-streaming scenarios

Configuration

  • Typing timer enabled (default StartTypingTimer = true)
  • Streaming optional easier

Minimal repro sketch

  1. Enable the default typing timer.
  2. Ensure the bot processes multiple turns concurrently (multiple users or rapid messages).
  3. Add an artificial delay so turns overlap.
  4. Attempt to send a message or streaming update.

Expected behavior

Sending a message or streaming update should never block indefinitely due to typing timer coordination. Typing timer shutdown should be scoped to the current turn/timer instance and respect cancellation or timeouts.


Actual behavior

One overlapping turn can block forever in _send.WaitOne(). The message is never delivered. No exception or timeout occurs. The turn never completes and diagnostic activities may never be flushed.


Why we believe this is the root cause

TypingTimer uses a static AutoResetEvent:

  • Declared as private static AutoResetEvent _send;
  • Reassigned per timer start via _send = new AutoResetEvent(false);

Because _send is static and overwritten by concurrent turns, one turn may wait on an event instance that is never signaled (lost signal), resulting in a permanent block via WaitOne().


Screenshots

Typing indicator stays forever
Image


Hosting Information

  • How are you Hosting this: Azure
  • Are you deploying: Azure Container App
  • Are you using Azure Bot Services: Yes
  • What Client are you using: Microsoft Teams
  • What .net version is your build in: .NET 8

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions