You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In multiple methods ie client.search(), client.queryChannels() and client.queryUsers(), the client waits for the web socket connection like so
awaitthis.wsPromise
If await this.wsPromise is awaiting and some other code closes the connection at the right time, the client method, eg client.queryChannels, will hang forever. The promise neither resolves nor rejects. Even when a new connection is opened it doesn't resolve the original promise because a new promise is created and assigned to this.wsPromise. This hanging promise is especially problematic for us, as calls to queryChannels are in a queue and if one call doesn't resolve, effectively the whole app is in a bad state, and the list of channels won't update until the user force quits the app.
To manually reproduce the bug, these steps must occur:
One of the above listed methods, eg client.queryChannels, calls await this.wsPromise while the websocket is connecting
Shortly after closeConnection() is called. For example, in our app, we call closeConnection whenever the React Native app transitions into background mode. The timing of calling closeConnection() is very important to reproduce this race condition. It must be called right after the new Websocket(url) is created and before onmessage is fired. This is more likely to occur with poor network conditions as it takes longer for the connection to be established.
The await this.wsPromise from step 1 is not rejected at this point, instead it continues waiting until there is an active connection. I think this makes sense as a behaviour. The alternative would be to reject the promise. I think it's fine though to keep the promise waiting for an active connection as this will lead to the best DX/UX, IMO.
A new connection is opened, eg once the app is reopened
Instead of resolving the original promise a new promise is created and the client.queryChannels call is left hanging forever.
Solution
To keep the spirit of the intended original await this.wsPromise code and produce the least code changes to your codebase you can turn wsPromise into a getter on StreamChat. The getter will construct a new promise and it will resolve whenever there is a healthy websocket connection.
getwsPromise(){constisConnected=(this.wsConnection?.isHealthy||this.wsFallback?.isHealthy())&&this._hasConnectionID();// Already connected so resolveif(isConnected)returnPromise.resolve();returnnewPromise<void>((resolve)=>{const{ unsubscribe }=this.on('connection.changed',(event)=>{if(event.online){unsubscribe();resolve();}});});}
Let me know if you have any questions
The text was updated successfully, but these errors were encountered:
mfbx9da4
changed the title
Race condition can lead to hanging promises
Race condition can lead to hanging promises as methods continue to await a web socket which has already been closed
May 12, 2023
mfbx9da4
changed the title
Race condition can lead to hanging promises as methods continue to await a web socket which has already been closed
Race condition: Closing a connection can lead to a hanging promises as methods continue to await a web socket which has already been closed
May 12, 2023
mfbx9da4
changed the title
Race condition: Closing a connection can lead to a hanging promises as methods continue to await a web socket which has already been closed
Race condition: Closing a connection can lead to infinitely hanging promises as methods continue to await a web socket which has already been closed
May 12, 2023
mfbx9da4
changed the title
Race condition: Closing a connection can lead to infinitely hanging promises as methods continue to await a web socket which has already been closed
Race condition: Closing a connection can lead to hanging promises as methods continue to await a web socket which has already been closed
May 12, 2023
In multiple methods ie
client.search()
,client.queryChannels()
andclient.queryUsers()
, the client waits for the web socket connection like soIf
await this.wsPromise
is awaiting and some other code closes the connection at the right time, the client method, egclient.queryChannels
, will hang forever. The promise neither resolves nor rejects. Even when a new connection is opened it doesn't resolve the original promise because a new promise is created and assigned tothis.wsPromise
. This hanging promise is especially problematic for us, as calls toqueryChannels
are in a queue and if one call doesn't resolve, effectively the whole app is in a bad state, and the list of channels won't update until the user force quits the app.Reproduction
I have created a minimal reproduction demo to demonstrate the problem in action. The demo also includes a proposed fix.
To manually reproduce the bug, these steps must occur:
client.queryChannels
, callsawait this.wsPromise
while the websocket is connectingcloseConnection()
is called. For example, in our app, we callcloseConnection
whenever the React Native app transitions into background mode. The timing of callingcloseConnection()
is very important to reproduce this race condition. It must be called right after thenew Websocket(url)
is created and beforeonmessage
is fired. This is more likely to occur with poor network conditions as it takes longer for the connection to be established.await this.wsPromise
from step 1 is not rejected at this point, instead it continues waiting until there is an active connection. I think this makes sense as a behaviour. The alternative would be to reject the promise. I think it's fine though to keep the promise waiting for an active connection as this will lead to the best DX/UX, IMO.client.queryChannels
call is left hanging forever.Solution
To keep the spirit of the intended original
await this.wsPromise
code and produce the least code changes to your codebase you can turnwsPromise
into a getter onStreamChat
. The getter will construct a new promise and it will resolve whenever there is a healthy websocket connection.Let me know if you have any questions
The text was updated successfully, but these errors were encountered: