Skip to content

Fix race condition and deadlock in DefaultShard.SendAsync - #136

Closed
Quahu wants to merge 4 commits into
masterfrom
fix-gateway-race-condition-16641127480768376837
Closed

Fix race condition and deadlock in DefaultShard.SendAsync#136
Quahu wants to merge 4 commits into
masterfrom
fix-gateway-race-condition-16641127480768376837

Conversation

@Quahu

@Quahu Quahu commented Feb 16, 2026

Copy link
Copy Markdown
Owner

This PR addresses a race condition and potential deadlock in DefaultShard.SendAsync.

The issue involved:

  1. Payloads requiring authentication (like UpdatePresence) being sent before the shard was authenticated, leading to immediate disconnection by the gateway with NotAuthenticated.
  2. Handshake payloads (like Identify) waiting for the shard to be Ready upon failure, causing a deadlock because Identify is required to make the shard Ready.

The fix:

  • Adds RequiresAuthentication(GatewayPayloadOperation op) to identify payloads that must wait for the Ready state.
  • Adds IsHandshake(GatewayPayloadOperation op) to identify payloads that should not wait for the Ready state on failure.
  • Modifies SendAsync to enforce WaitForReadyAsync for authenticated payloads.
  • Modifies SendAsync's catch block to bypass WaitForReadyAsync for handshake payloads, allowing the connection loop to retry instead of deadlocking.

PR created automatically by Jules for task 16641127480768376837 started by @Quahu

- Added `RequiresAuthentication` and `IsHandshake` helper methods.
- `SendAsync` now waits for `Ready` state if the payload requires authentication (e.g., UpdatePresence, RequestMembers).
- `SendAsync` no longer waits for `Ready` state if a handshake payload (Identify, Resume) fails, preventing a deadlock where the handshake waits for itself to complete.
- This resolves issues where payloads requiring authentication were sent before the session was ready, causing immediate disconnection.

Co-authored-by: Quahu <24848652+Quahu@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

google-labs-jules Bot and others added 3 commits February 16, 2026 00:44
- Implemented a `SemaphoreSlim` (`_sendLock`) to synchronize `SendAsync` with `InternalRunAsync`.
- `SendAsync` now acquires the lock for application payloads (requiring authentication) to ensure the gateway state is stable and Ready.
- `InternalRunAsync` acquires the lock when transitioning to a non-Ready state or closing the gateway, preventing concurrent sends during disconnection/reconnection.
- Handshake payloads (`Identify`, `Resume`) and `Heartbeat` bypass the lock to avoid deadlocks and ensure connectivity maintenance.
- Added `RequiresAuthentication` and `IsHandshake` helpers.
- `SendAsync` loops and waits for `Ready` state if the lock acquisition reveals the state has changed.

Co-authored-by: Quahu <24848652+Quahu@users.noreply.github.com>
- Implemented a `SemaphoreSlim` (`_sendLock`) to synchronize `SendAsync` with `InternalRunAsync`.
- `SendAsync` now acquires the lock for application payloads (requiring authentication) to ensure the gateway state is stable and Ready.
- `InternalRunAsync` acquires the lock when transitioning to a non-Ready state or closing the gateway, preventing concurrent sends during disconnection/reconnection.
- Handshake payloads (`Identify`, `Resume`) and `Heartbeat` bypass the lock to avoid deadlocks and ensure connectivity maintenance.
- Added `RequiresAuthentication` and `IsHandshake` helpers.
- `SendAsync` loops and waits for `Ready` state if the lock acquisition reveals the state has changed.
- Cleaned up internal implementation details and comments.

Co-authored-by: Quahu <24848652+Quahu@users.noreply.github.com>
- Implemented `RequiresAuthentication` and `IsHandshake` helpers to classify payload operations.
- Modified `SendAsync` to strictly wait for `Ready` state before sending payloads that require authentication (e.g., UpdatePresence, RequestMembers).
- Updated `SendAsync` retry logic to bypass waiting for `Ready` state if the failed payload is a handshake operation (Identify, Resume, Heartbeat), preventing deadlocks.
- Simplified synchronization logic by removing unnecessary Semaphores and relying on existing `WaitForReadyAsync` and task completion sources.
- This resolves issues where authenticated payloads were sent prematurely during reconnection, causing immediate disconnection.

Co-authored-by: Quahu <24848652+Quahu@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant