Skip to content

Start new event queue when old one has expired #185

Closed
@gnprice

Description

@gnprice
Member

As described in #184, when a long-poll for events fails, the current prototype just stops polling. That issue is about transient failures; this one is for an important, non-transient, but recoverable, class of error.

Namely, if the long-poll fails because the event queue is gone, we should create a new event queue and go from there. Here's from zulip-mobile at startEventPolling:

    while (true) {
      // …
      try {
        const response = await api.pollForEvents(auth, queueId, lastEventId);
        // …
      } catch (errorIllTyped) {
        const e: mixed = errorIllTyped; // https://github.com/facebook/flow/issues/2470
        // We had an error polling the server for events.

        // …

        if (e instanceof ApiError && e.code === 'BAD_EVENT_QUEUE_ID') {
          // The event queue is too old or has been garbage collected.
          dispatch(deadQueue()); // eslint-disable-line no-use-before-define
          break;

and we can use similar logic for detecting this condition.

For what to do once the condition is detected, zulip-mobile is not as good a model and we should think it through afresh. We'll need to register a new event queue, get a new initial snapshot, re-initialize data from there, and resume polling. For any open message lists, we'll also want to re-fetch messages, as that's the one major area of the data model that isn't covered by the initial snapshot. (For any minor such areas, we'll want to similarly refresh any data we have.)

I think the cleanest way to approach this will be to actually create a new PerAccountStore for the fresh data, and then tell each PerAccountStoreWidget that had the old store to replace it with the new one. Further thoughts:

  • For the bulk of our application code, this should work smoothly straight out of the box. That's because the normal pattern is to say final store = PerAccountStoreWidget.of(context); whenever in need of a PerAccountStore, and never to hang on to a PerAccountStore beyond the end of the build method or UI-event handler. That .of method creates a dependency on the ancestor widget; so when there's a new store, the descendant element will get notified and rebuilt. The rebuild will get the store afresh, as always, and therefore use the new store and won't even notice that it isn't the same object as the old store.
    • This underlines why the proper idiom is to always get a PerAccountStore fresh like that, and not to hang onto one or pass one around. Instead, pass around a BuildContext.
  • After creating a fresh PerAccountStore, we can go on to create fresh MessageListView objects corresponding to each of those that are currently registered, and have them fetch messages corresponding to the range of messages the existing views have.
    • This is one of the advantages of making a fresh PerAccountStore — those MessageListViews can refer to the new PerAccountStore while the old MessageListViews continue to refer to the old store, and there's no risk of inconsistent data from having an old view-model refer to a store with new data or vice versa.
    • → Split this extra bit of polish out as msglist: On new event queue, try to re-fetch messages before switching to new store #464.
  • The PerAccountStore can also grow a field like bool isStale, which can be set to true immediately when the expired queue is detected, while we're still working on fetching data for the new store. Then that field can drive a "Connecting…" banner, or equivalent UI for letting the user know that the data they're looking at may be out of date and the app is working on it.

Metadata

Metadata

Assignees

Labels

a-apiImplementing specific parts of the Zulip server APIa-modelImplementing our data model (PerAccountStore, etc.)a-syncEvent queue; retry; local echo; racesbeta feedbackThings beta users have specifically asked for

Type

No type

Projects

Status

Done

Relationships

None yet

    Development

    Participants

    @gnprice

    Issue actions

      Start new event queue when old one has expired · Issue #185 · zulip/zulip-flutter