-
Notifications
You must be signed in to change notification settings - Fork 310
store: Replace event queue on expiry #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
In order to test this manually in the app, I used the following change locally: --- a/lib/api/route/events.dart
+++ b/lib/api/route/events.dart
@@ -10,4 +10,5 @@ part 'events.g.dart';
Future<InitialSnapshot> registerQueue(ApiConnection connection) {
return connection.post('registerQueue', InitialSnapshot.fromJson, 'register', {
+ 'queue_lifespan_secs': 1,
'apply_markdown': true,
'slim_presence': true, and pointed it at my dev server, with a handful of local commits there which can be found in my |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Glad to be fixing this really visible behavior problem in the app.
I did notice some buggy behavior when I opened the "All messages" view and watched (with your helpful manual-testing steps; thanks!) how the app responded as event queues were invalidated: (link to video; you might want to watch with increased playback speed)
Possibly related to something I noticed in the code; comments below.
lib/model/store.dart
Outdated
@override | ||
void dispose() { | ||
recentDmConversationsView.dispose(); | ||
for (final view in _messageListViews) { | ||
view.dispose(); | ||
} | ||
super.dispose(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also dispose unreads
, right? I think that's currently the only other thing with a dispose
method that needs to be called here. It seems like the kind of thing that's easy to forget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah, I guess that's a ChangeNotifier
and so does have a dispose
. Good catch, thanks.
It seems like the kind of thing that's easy to forget.
Yeah.
@@ -49,13 +58,17 @@ class TestGlobalStore extends GlobalStore { | |||
} | |||
|
|||
@override | |||
Future<PerAccountStore> loadPerAccount(Account account) { | |||
return Future.value(PerAccountStore.fromInitialSnapshot( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to remove the Future.value
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a reason it needs to be. Sure, maybe it's clearer with the Future.value
.
lib/model/store.dart
Outdated
for (final view in _messageListViews) { | ||
view.dispose(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I have a message list open, I'm getting a concurrent modification error here, because this call to MessageListView.dispose
—which is in a loop over _messageListViews
—causes a call to _messageListViews.remove
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm thanks. I guess I'll need to untangle that.
lib/widgets/message_list.dart
Outdated
@@ -186,7 +186,7 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat | |||
} | |||
|
|||
@override | |||
void onNewStore() { | |||
void onNewStore() { // TODO(#464) try to keep using old model until new one gets messages | |||
model?.dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. model
, here, refers to the message-list view on the old per-account store. In the case of refreshing a dead event queue, those things (the old message-list view and the old per-account store) will have already been disposed, by the UpdateMachine
, and so I think it's not appropriate to do anything with them, including this dispose
call.
I was alerted to this by the following assert
in PerAccountStore.unregisterMessageList
, which was throwing in my manual testing:
void unregisterMessageList(MessageListView view) {
final removed = _messageListViews.remove(view);
assert(removed);
}
One maybe-naïve fix is to just remove the model?.dispose();
line in this onNewStore
. But the TODO(#464)
seems fundamentally impossible to accomplish as long as the old model is disposed by the UpdateMachine
before _MessageListState
even learns (by a call to its onNewStore
) that it has to refetch messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was alerted to this by the following
assert
inPerAccountStore.unregisterMessageList
, which was throwing in my manual testing:
Hmm, good catch, thanks.
will have already been disposed, by the
UpdateMachine
, and so I think it's not appropriate to do anything with them, including thisdispose
call.
In general dispose
should be idempotent — the usual spec for it is "it's no longer valid to do anything with this object, except call dispose
again".
… Or maybe not. Looking around for examples, both ChangeNotifier.dispose
and State.dispose
have asserts that will fail if you dispose twice.
One maybe-naïve fix is to just remove the
model?.dispose();
line in thisonNewStore
. But theTODO(#464)
seems fundamentally impossible to accomplish as long as the old model is disposed by theUpdateMachine
before_MessageListState
even learns (by a call to itsonNewStore
) that it has to refetch messages.
Yeah, #464 will definitely call for the PerAccountStore.dispose
to get delayed. One thing that'll be a bit tricky in implementing that issue will be to ensure that it does eventually get disposed — something like, when it's already stale and then its last MessageListView
goes away.
But in the meantime, this code can be written for the way that PerAccountStore.dispose
currently works — so it can just drop this dispose
call.
Wacky, yeah, thanks. Even with that assert throwing, I'm not sure quite how it ends up with that symptom. I guess the exception propagates up into the framework where it's trying to update the tree, and it ends up with the old MessageList hanging around while it adds a new one as a sibling. Anyway, presumably fixing that assert will fix it. It seems like I must have neglected to repeat the manual testing after I added the |
Thanks for the review! Revision pushed. I found there were also some |
…ntStore This change happened in 5c778bd, but some comments didn't get updated at the same time, oops.
This will be useful for tests.
The `shortHash` identity tags can be helpful in debugging.
This makes GlobalStore behave a bit more conventionally for a ChangeNotifier.
Thanks, LGTM! Merged, after manually testing the fix. |
Fixes: #185