mirror of
https://github.com/block/buzz.git
synced 2026-11-15 13:51:12 +00:00
fix(mobile): retire directory-only loads on a scope switch
A directory-only search load kept its ownership marker after a community or identity switch retired it. On returning to the first community, the list refresh treated the abandoned loading status as owned by a live load and left it in place, so search never loaded the directory again. A scope switch now retires directory-only loads too: it advances the directory generation and clears the marker. Tests cover returning both while the old request is in flight and after it ended. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Signed-off-by: Tom Brow <tomb@squareup.com>
This commit is contained in:
@@ -459,6 +459,16 @@ class _ChannelRefreshCoordinator {
|
||||
/// wire cannot be written into the new scope.
|
||||
void retireInFlight() => _generation++;
|
||||
|
||||
/// Retires every in-flight request, directory-only loads included, for a
|
||||
/// relay or identity change. A directory-only load survives ordinary list
|
||||
/// refreshes, but not a scope change: its loading status must not look
|
||||
/// owned when the user returns to the old scope.
|
||||
void retireScope() {
|
||||
_generation++;
|
||||
_directoryGeneration++;
|
||||
_directoryOnlyGeneration = null;
|
||||
}
|
||||
|
||||
/// Starts a fenced refresh without issuing the directory query.
|
||||
///
|
||||
/// Used by callers that must carry the scope across later awaits even when
|
||||
|
||||
@@ -228,7 +228,7 @@ class ChannelsNotifier extends AsyncNotifier<List<Channel>> {
|
||||
_confirmedJoins.clear();
|
||||
// Retire any in-flight directory request: its response describes the
|
||||
// previous relay or identity and must not reach this scope's state.
|
||||
_refreshCoordinator.retireInFlight();
|
||||
_refreshCoordinator.retireScope();
|
||||
}
|
||||
final snapshot =
|
||||
(_hasLoaded ? state.value : null) ??
|
||||
|
||||
@@ -1963,6 +1963,107 @@ void main() {
|
||||
isNot(communityB),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'returning to a community during a discarded load loads again',
|
||||
() async {
|
||||
final session = _FakeRelaySession(
|
||||
memberships: [_membership(_channelA, myPk)],
|
||||
metadata: [
|
||||
_meta(id: _channelA, name: 'general'),
|
||||
_meta(id: _channelB, name: 'community-a-open'),
|
||||
],
|
||||
);
|
||||
final container = await loaded(session);
|
||||
final communityA = container.read(relayConfigProvider).baseUrl;
|
||||
|
||||
session.pauseNextDirectoryQuery();
|
||||
final directory = container
|
||||
.read(channelsProvider.notifier)
|
||||
.ensureDirectoryLoaded(maxAge: Duration.zero);
|
||||
await session.nextDirectoryQueryStarted;
|
||||
|
||||
container
|
||||
.read(relayConfigProvider.notifier)
|
||||
.update(baseUrl: 'https://community-b.example');
|
||||
await container.read(channelsProvider.future);
|
||||
container
|
||||
.read(relayConfigProvider.notifier)
|
||||
.update(baseUrl: communityA);
|
||||
await container.read(channelsProvider.future);
|
||||
await _settle();
|
||||
final directoryQueries = session.directoryQueryFilters.length;
|
||||
await container
|
||||
.read(channelsProvider.notifier)
|
||||
.ensureDirectoryLoaded(maxAge: Duration.zero);
|
||||
|
||||
expect(
|
||||
session.directoryQueryFilters.length,
|
||||
greaterThan(directoryQueries),
|
||||
);
|
||||
expect(
|
||||
container.read(channelDirectoryLoadStatusProvider).status,
|
||||
ChannelDirectoryLoadStatus.loaded,
|
||||
);
|
||||
|
||||
// The old request ending later leaves the newer load's status alone.
|
||||
session.resumePausedDirectoryQuery();
|
||||
await directory;
|
||||
await _settle();
|
||||
expect(
|
||||
container.read(channelDirectoryLoadStatusProvider).status,
|
||||
ChannelDirectoryLoadStatus.loaded,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'returning to a community after a discarded load loads again',
|
||||
() async {
|
||||
final session = _FakeRelaySession(
|
||||
memberships: [_membership(_channelA, myPk)],
|
||||
metadata: [
|
||||
_meta(id: _channelA, name: 'general'),
|
||||
_meta(id: _channelB, name: 'community-a-open'),
|
||||
],
|
||||
);
|
||||
final container = await loaded(session);
|
||||
final communityA = container.read(relayConfigProvider).baseUrl;
|
||||
|
||||
session.pauseNextDirectoryQuery();
|
||||
final directory = container
|
||||
.read(channelsProvider.notifier)
|
||||
.ensureDirectoryLoaded(maxAge: Duration.zero);
|
||||
await session.nextDirectoryQueryStarted;
|
||||
|
||||
container
|
||||
.read(relayConfigProvider.notifier)
|
||||
.update(baseUrl: 'https://community-b.example');
|
||||
await container.read(channelsProvider.future);
|
||||
session.resumePausedDirectoryQuery();
|
||||
await directory;
|
||||
await _settle();
|
||||
|
||||
container
|
||||
.read(relayConfigProvider.notifier)
|
||||
.update(baseUrl: communityA);
|
||||
await container.read(channelsProvider.future);
|
||||
await _settle();
|
||||
final directoryQueries = session.directoryQueryFilters.length;
|
||||
await container
|
||||
.read(channelsProvider.notifier)
|
||||
.ensureDirectoryLoaded(maxAge: Duration.zero);
|
||||
|
||||
expect(
|
||||
session.directoryQueryFilters.length,
|
||||
greaterThan(directoryQueries),
|
||||
);
|
||||
expect(
|
||||
container.read(channelDirectoryLoadStatusProvider).status,
|
||||
ChannelDirectoryLoadStatus.loaded,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('membership refresh does not refetch a loaded directory', () async {
|
||||
@@ -3490,6 +3591,7 @@ class _FakeRelaySession extends RelaySessionNotifier {
|
||||
Completer<void>? _pausedSubscribe;
|
||||
Completer<void>? _subscribeStarted;
|
||||
Completer<void>? _pausedDirectory;
|
||||
bool _pauseClaimed = false;
|
||||
Completer<void>? _directoryStarted;
|
||||
Completer<void>? _pausedHiddenDm;
|
||||
Completer<void>? _hiddenDmStarted;
|
||||
@@ -3812,11 +3914,15 @@ class _FakeRelaySession extends RelaySessionNotifier {
|
||||
// active when the response is released.
|
||||
final directorySnapshot = List.of(metadata);
|
||||
final paused = _pausedDirectory;
|
||||
if (paused != null) {
|
||||
// Only the first query after the pause waits; a later one meanwhile
|
||||
// (a new scope's load) answers at once.
|
||||
if (paused != null && !_pauseClaimed) {
|
||||
_pauseClaimed = true;
|
||||
_directoryStarted!.complete();
|
||||
await paused.future;
|
||||
_pausedDirectory = null;
|
||||
_directoryStarted = null;
|
||||
_pauseClaimed = false;
|
||||
}
|
||||
if (directoryFailures > 0) {
|
||||
directoryFailures--;
|
||||
|
||||
Reference in New Issue
Block a user