Remora.Discord icon indicating copy to clipboard operation
Remora.Discord copied to clipboard

[Bug]: RunAsync fails to disconnect properly from the gateway

Open Kaoticz opened this issue 2 years ago • 4 comments

Description

DiscordGatewayClient.RunAsync() doesn't seem to be properly disconnecting from the gateway when its CancellationToken is cancelled.

According to the official documentation:

When you close the connection to the gateway with close code 1000 or 1001, your session will be invalidated and your bot will appear offline.

If you simply close the TCP connection or use a different close code, the session will remain active and timeout after a few minutes.

The latter seems to be always happening, no matter what.

Steps to Reproduce

Execute DiscordGatewayClient.RunAsync(), then cancel the CancellationToken passed to it. RunAsync will stop running, but the bot will still show as online on Discord for a couple of minutes.

Expected Behavior

The bot shuts down and shows as offline on Discord.

Current Behavior

The bot shuts down, but still shows as online on Discord for a couple of minutes.

Library / Runtime Information

.NET: 6.0.110 Remora.Discord: 2022.49.0

Kaoticz avatar Oct 30 '22 21:10 Kaoticz

I suspect this line may be the cause, as passing a cancelled token to this method would cause it to throw (which is caught), but that doesn't ensure the socket is properly closed. The correct close code would be sent otherwise.

VelvetToroyashi avatar Oct 30 '22 21:10 VelvetToroyashi

In fact, very deep down, the CT will actually abort the socket (source), which also disposes of said socket, rendering it unusable. (source)

Interestingly, this is somewhat noted in D#+, which passes CancellationToken.None instead.

This also poses an issue, since iirc the same CancellationToken is passed to ReceiveAsync, which will also abort the socket.

We could do what D#+ does and read in chunks, and check for cancellation before/after reading

VelvetToroyashi avatar Oct 30 '22 21:10 VelvetToroyashi

I worked around this in my common gateway refactor by using an internal CancellationTokenSource for receive operations in the WebSocketPayloadTransportService. Said token was cancelled when DisconnectAsync was called, and the receive loop watched for cancellation of the token passed to ReceiveAsync in order to exit as soon as possible. Much like using CancellationToken.None this makes the assumption that websocket messages will be received quickly enough to exit the receive method in a reasonable amount of time.

carlst99 avatar Oct 31 '22 08:10 carlst99

No promises, but I have little better to do so I'll see about fixing this 👍🏽

VelvetToroyashi avatar Dec 05 '22 02:12 VelvetToroyashi