gramjs icon indicating copy to clipboard operation
gramjs copied to clipboard

Unresolved promises after disconnect resulting in race condition.

Open halmos opened this issue 3 years ago • 7 comments

After calling the client disconnect() command, I've found that a pending promise can resolve after the the disconnect resulting in the [ERROR] - [Error: Cannot send requests while disconnected. You need to call .connect()] error.

This line sleeps for 9 seconds https://github.com/gram-js/gramjs/blob/master/gramjs/client/updates.ts#L172

before calling the timeout() callback: https://github.com/gram-js/gramjs/blob/master/gramjs/client/updates.ts#L180

By the time the callback occurs the client may have already disconnected. client._sender.send() then results in the error mentioned above.

It also appears that the property _destroyed is never set, so that this while statement is stuck in an infinite loop.

https://github.com/gram-js/gramjs/blob/master/gramjs/client/updates.ts#L171

I'm not sure what the best solution is to fix the async race condition here. Somehow these sleep and timeout promises need to be canceled when the client disconnect() is called. Or perhaps the disconnect() method needs to wait for the pending timeout() to resolve before resolving itself.

As a temporary workaround I'm using:

 myClient._destroyed = true;
 await sleep(9000);

In this way I can force the while loop to stop and then wait the PING_INTERVAL period to make sure the pending promises resolve.

halmos avatar Jan 27 '22 14:01 halmos

we need to keep pinging telegram or otherwise they will close the connection so there is a background task that pings once in a while.

JS doesn't allow you to cancel a promise so we can't really kill it after disconnect.

Usually when someone calls .disconnect they are planning to reconnect again so it doesn't make sense to completely stop the loop (also a lot of internal functions call .disconnect()) The loop should only stop once you call .destroy .destroy() should be setting ._destroyed to true but currently it's not so that's a bug./

painor avatar Jan 27 '22 14:01 painor

Ah, yes, that makes sense. Maybe the timeout callback should check the connection status before calling client._sender.send() ? At least that would avoid the error being thrown.

halmos avatar Jan 27 '22 15:01 halmos

Does destroy require that the Telegram session be completely re-authenticated with phone-number, etc?

halmos avatar Jan 27 '22 15:01 halmos

it shouldn't, it just tries to kill all senders/event listeners

painor avatar Jan 27 '22 15:01 painor

I've pushed a small fix to 2.4.5. it might fix it.

painor avatar Jan 27 '22 15:01 painor

This is working great. Many thanks!

halmos avatar Jan 27 '22 16:01 halmos

JS doesn't allow you to cancel a promise so we can't really kill it after disconnect.

You can reject the promises. Promises never rejected or resolved are a really big problem to app, because can generate memory leaks.

For example I have situations when I try download two files at same time, Gramjs disconnects one connection while getting the data, and the promise never resolve/reject.

voxsoftware avatar May 20 '22 18:05 voxsoftware