TryQuiet / quiet

A private, p2p alternative to Slack and Discord built on Tor & IPFS
https://www.tryquiet.org
GNU General Public License v3.0
1.98k stars 86 forks source link

Multi forking root saga #1970

Open kingalg opened 1 year ago

kingalg commented 1 year ago

Version: mobile@2.0.1-alpha.7 System: iOS and android (but less common)

What happen - user is stuck at thee beginning of joining proces after very specific conditions are fulfilled

Steps to recreate:

  1. Have an app with already registered user and Leave Community (this step is crucial as it won't happen with freshly installed app)
  2. Change screen "join community" to "create community"
  3. Put the app in the background
  4. Try to join with QR code

App should recognize that this is the code to join community and proceed with the usual flow)

Edit: Initially I thought that it happens only on ios but I've spotted it on android as well, even without changing to "create community".

Edit2 (29 Nov): it happens especially often when the user leaves and tries to re-join community.

kingalg commented 11 months ago

@siepra I didn't notice any improvement in mobile@2.0.3-alpha.13 (iOS 340). Do you have some other ideas what may be causing this problems?

siepra commented 11 months ago

Initially I thought it was the problem with web socket client connection, but it's actually the problem with redux persist being purged when leaving community.

EDIT: I performed tests including commenting out pausing, flushing and purging redux persist. It seemed to make no impact

holmesworcester commented 10 months ago

This will affect users leaving and rejoining communities who do not "swipe up" or close the app after leaving and before re-joining a new community.

@siepra will add notes about what he's found so far.

siepra commented 10 months ago

My observation is, once left the community state-manager root saga is being forked multiple times:

 LOG  Leaving community
 LOG  closing socket connection
 LOG  canceling root task
 INFO  Clearing redux store
 LOG  websocket connected
 LOG  WEBSOCKET Forking state-manager sagas {"@@redux-saga/TASK": true, "cancel": [Function cancel], "cont": [Function anonymous], "context": {}, "end": [Function end], "error": [Function error], "id": 8699, "isAborted": [Function isAborted], "isCancelled": [Function isCancelled], "isRoot": undefined, "isRunning": [Function isRunning], "joiners": [], "meta": {"location": undefined, "name": "useIO"}, "queue": {"abort": [Function abort], "addTask": [Function addTask], "cancelAll": [Function cancelAll], "getTasks": [Function getTasks]}, "result": [Function result], "setContext": [Function setContext], "toPromise": [Function toPromise]}
 INFO  Flushing redux store
 INFO  Flushing redux store
 INFO  Flushing redux store
 INFO  Flushing redux store
 LOG  closing socket connection
 LOG  canceling root task
 LOG  canceling root task
 LOG  INIT_NAVIGATION: Starting deep link flow.
 LOG  INIT_NAVIGATION: Waiting for websocket connection before proceeding with deep link flow.
 LOG  INIT_NAVIGATION: Waiting for websocket connection before proceeding with deep link flow.
 LOG  websocket connected
 LOG  WEBSOCKET Forking state-manager sagas {"@@redux-saga/TASK": true, "cancel": [Function cancel], "cont": [Function anonymous], "context": {}, "end": [Function end], "error": [Function error], "id": 8947, "isAborted": [Function isAborted], "isCancelled": [Function isCancelled], "isRoot": undefined, "isRunning": [Function isRunning], "joiners": [], "meta": {"location": undefined, "name": "useIO"}, "queue": {"abort": [Function abort], "addTask": [Function addTask], "cancelAll": [Function cancelAll], "getTasks": [Function getTasks]}, "result": [Function result], "setContext": [Function setContext], "toPromise": [Function toPromise]}
 LOG  INIT_NAVIGATION: Continuing on deep link flow.
 LOG  Stored invitation codes []
 LOG  Current invitation codes [{"onionAddress": "3onqrkijcdiesy6eemsbb52dsqenr3srrm6f6zek62zscog7zsq4c4id", "peerId": "QmPGNv63roaTE1vrKpG8kNoynk7YYWTKtXx3AeisHRzdno"}]
 LOG  Is invitation data valid true
 LOG  INIT_NAVIGATION: Proceeding with connection to the community.
 LOG  INIT_NAVIGATION: Switching to the join community screen.
 LOG  create network saga
 LOG  create network saga: saving PSK
 LOG  INIT_NAVIGATION: Continuing on deep link flow.
 LOG  Stored invitation codes [{"onionAddress": "3onqrkijcdiesy6eemsbb52dsqenr3srrm6f6zek62zscog7zsq4c4id", "peerId": "QmPGNv63roaTE1vrKpG8kNoynk7YYWTKtXx3AeisHRzdno"}]
 LOG  Current invitation codes [{"onionAddress": "3onqrkijcdiesy6eemsbb52dsqenr3srrm6f6zek62zscog7zsq4c4id", "peerId": "QmPGNv63roaTE1vrKpG8kNoynk7YYWTKtXx3AeisHRzdno"}]
 LOG  Is invitation data valid true
 LOG  INIT_NAVIGATION: Proceeding with connection to the community.
 LOG  INIT_NAVIGATION: Switching to the join community screen.
 LOG  create network saga
 LOG  create network saga: saving PSK
 LOG  INIT_NAVIGATION: Switching to the username registration screen.
 LOG  INIT_NAVIGATION: Switching to the username registration screen.
 LOG  WEBSOCKET Entered start connection saga true
 LOG  WEBSOCKET Entered start connection saga true
 LOG  WEBSOCKET Entered start connection saga true
 LOG  WEBSOCKET Entered start connection saga true
 LOG  websocket connected
 LOG  websocket connected
 LOG  websocket connected
 LOG  WEBSOCKET Forking state-manager sagas {"@@redux-saga/TASK": true, "cancel": [Function cancel], "cont": [Function anonymous], "context": {}, "end": [Function end], "error": [Function error], "id": 9243, "isAborted": [Function isAborted], "isCancelled": [Function isCancelled], "isRoot": undefined, "isRunning": [Function isRunning], "joiners": [], "meta": {"location": undefined, "name": "useIO"}, "queue": {"abort": [Function abort], "addTask": [Function addTask], "cancelAll": [Function cancelAll], "getTasks": [Function getTasks]}, "result": [Function result], "setContext": [Function setContext], "toPromise": [Function toPromise]}
 LOG  WEBSOCKET Forking state-manager sagas {"@@redux-saga/TASK": true, "cancel": [Function cancel], "cont": [Function anonymous], "context": {}, "end": [Function end], "error": [Function error], "id": 9425, "isAborted": [Function isAborted], "isCancelled": [Function isCancelled], "isRoot": undefined, "isRunning": [Function isRunning], "joiners": [], "meta": {"location": undefined, "name": "useIO"}, "queue": {"abort": [Function abort], "addTask": [Function addTask], "cancelAll": [Function cancelAll], "getTasks": [Function getTasks]}, "result": [Function result], "setContext": [Function setContext], "toPromise": [Function toPromise]}
 LOG  websocket connected
 LOG  WEBSOCKET Forking state-manager sagas {"@@redux-saga/TASK": true, "cancel": [Function cancel], "cont": [Function anonymous], "context": {}, "end": [Function end], "error": [Function error], "id": 9613, "isAborted": [Function isAborted], "isCancelled": [Function isCancelled], "isRoot": undefined, "isRunning": [Function isRunning], "joiners": [], "meta": {"location": undefined, "name": "useIO"}, "queue": {"abort": [Function abort], "addTask": [Function addTask], "cancelAll": [Function cancelAll], "getTasks": [Function getTasks]}, "result": [Function result], "setContext": [Function setContext], "toPromise": [Function toPromise]}
 LOG  WEBSOCKET Forking state-manager sagas {"@@redux-saga/TASK": true, "cancel": [Function cancel], "cont": [Function anonymous], "context": {}, "end": [Function end], "error": [Function error], "id": 9795, "isAborted": [Function isAborted], "isCancelled": [Function isCancelled], "isRoot": undefined, "isRunning": [Function isRunning], "joiners": [], "meta": {"location": undefined, "name": "useIO"}, "queue": {"abort": [Function abort], "addTask": [Function addTask], "cancelAll": [Function cancelAll], "getTasks": [Function getTasks]}, "result": [Function result], "setContext": [Function setContext], "toPromise": [Function toPromise]}

It's as though multiple apps were running concurrently, sharing the same UI

siepra commented 9 months ago

I observed web socket client connecting more than once.

I think it has something to do with connection-manager's closeAllServices method, especially this part:

    if (this.serverIoProvider?.io) {
      this.logger('Closing socket server')
      this.serverIoProvider.io.close()
    }

The issue appears on both platforms so I assume the problem is not related to lifecycle changes, yet Kinga first observed it on iOS which directly calls closeAllServices when moving to background.

It's easiest to reproduce when combined with leaving the community. It executes leaveCommunity method that nests closeAllServices.

It looks like even though it closed socket server, it reconnects the old client's socket under certain conditions (which I'm trying to figure out). Is that even possible? @EmiM

There's a quicker way to observe this behaviour on iOS (although it probably won't break anything but it'll show results in logs):

  1. Open the app
  2. Minimise it
  3. Use a joining link

Here are shorten logs I got during debugging

[javascript] 'client: Websocket connected', '-CuTj0qrm_AgFfubAAAt'
[...]
[javascript] 'client: Closing socket connection', '-CuTj0qrm_AgFfubAAAt'
[...]
[javascript] INIT_NAVIGATION: Starting deep link flow.
[javascript] INIT_NAVIGATION: Waiting for websocket connection before proceeding with deep link flow.
[javascript] 'client: Websocket connected', 'O62kaWdiNaPE0RLTAAAv'
[...]
[javascript] NATIVE MODULES: Starting websocket connection
[javascript] WEBSOCKET: startConnectionSaga
[javascript] 'client: Websocket connected', 'F0E9wZtQOog1ZOlRAAAx'

Here's a branch containing additional loggings https://github.com/TryQuiet/quiet/pull/2154

siepra commented 9 months ago

Details moved to #2253