kill 50ms sleep waits in client lib.rs tests#487
Merged
intendednull merged 1 commit intoApr 29, 2026
Conversation
Refs #271. Replace 10 `tokio::time::sleep(50ms)` "wait for actor" stalls in `crates/client/src/lib.rs` test module with deterministic synchronization. Per-call-site decision: - 6 sites where the read goes to the same StateActor that was just mutated: drop the sleep entirely. The actor mailbox is FIFO and every hop (`mutate`, `get`, `select`) is `ask`-based, so the mutation is visible to the next `ask` without any wall-clock wait. Sites: send_message_and_read_back, switch_server_updates_event_state (2x), generate_invite_grants_send_permission_to_recipient, mutate_channel_mute_emits_event_and_flips_stats, mutate_channel_mute_toggle_off_clears_set. - 4 sites where the read goes through a `DerivedActor` whose recompute is genuinely async (Notify -> spawn snapshot -> do_send(UpdateCache)): replace with a polling helper `await_view(probe)` that yields between `get().await` attempts under a 5s deadline. Faster on quick machines, reliable on slow ones. Sites: presence_self_override_round_trip, presence_reachable_peer_defaults_to_here, presence_queued_then_gone_after_threshold (2x). Why polling rather than subscribing to `Notify`: subscribing after the mutation is racy (Notify may have already fired) and a fresh `get()` forces the cache; tight polling with `yield_now` is simpler and bounded. Same strategy as the existing `wait_for_message`/`wait_until` helpers in `crates/client/src/tests/multi_peer_sync.rs`. Test count: 303 client tests pass before and after; presence tests run 3x in a row clean.
intendednull
added a commit
that referenced
this pull request
Apr 29, 2026
#487) Refs #271. Replace 10 `tokio::time::sleep(50ms)` "wait for actor" stalls in `crates/client/src/lib.rs` test module with deterministic synchronization. Per-call-site decision: - 6 sites where the read goes to the same StateActor that was just mutated: drop the sleep entirely. The actor mailbox is FIFO and every hop (`mutate`, `get`, `select`) is `ask`-based, so the mutation is visible to the next `ask` without any wall-clock wait. Sites: send_message_and_read_back, switch_server_updates_event_state (2x), generate_invite_grants_send_permission_to_recipient, mutate_channel_mute_emits_event_and_flips_stats, mutate_channel_mute_toggle_off_clears_set. - 4 sites where the read goes through a `DerivedActor` whose recompute is genuinely async (Notify -> spawn snapshot -> do_send(UpdateCache)): replace with a polling helper `await_view(probe)` that yields between `get().await` attempts under a 5s deadline. Faster on quick machines, reliable on slow ones. Sites: presence_self_override_round_trip, presence_reachable_peer_defaults_to_here, presence_queued_then_gone_after_threshold (2x). Why polling rather than subscribing to `Notify`: subscribing after the mutation is racy (Notify may have already fired) and a fresh `get()` forces the cache; tight polling with `yield_now` is simpler and bounded. Same strategy as the existing `wait_for_message`/`wait_until` helpers in `crates/client/src/tests/multi_peer_sync.rs`. Test count: 303 client tests pass before and after; presence tests run 3x in a row clean. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #271.
ten
tokio::time::sleep(50ms)waits incrates/client/src/lib.rsget nuked. issue text say sleeps in production methods but file got reshuffled — they all in#[cfg(test)] mod testsblock now (line 1255+). same bad pattern, same fix.buckets per site
drop sleep, same StateActor, FIFO mailbox handle ordering (6 sites):
send_message_and_read_back—send_messageawaitapply_eventawaitmutate(event_state);messages()read same actorswitch_server_updates_event_stateaftersend_message— same as aboveswitch_server_updates_event_stateafterswitch_server—switch_serverawait all four mutations (dag, event_state, server_registry, chat_meta) before returngenerate_invite_grants_send_permission_to_recipient—generate_inviteend withapply_eventawait; testselectsame actormutate_channel_mute_emits_event_and_flips_stats— sleep was BEFOREsubscribe_events; broker only deliver to currently-subscribed recipients, so priorCreateChannelpublish cant leakmutate_channel_mute_toggle_off_clears_set— three await mutations on same event_state actorpoll helper, derived actor recompute genuinely async (4 sites):
presence_self_override_round_trip— mutatepresence_meta, readview_handle.presence(derived from 3 sources)presence_reachable_peer_defaults_to_here— mutatechat_metaviapeer_connected, read derived presencepresence_queued_then_gone_after_thresholdafterpeer_disconnected— same shapepresence_queued_then_gone_after_thresholdafter tick burst — same shapederived actor flow: Notify → spawn snapshot →
do_send(UpdateCache)→ cache replaced.get()return cached value, can be stale. fix = smallawait_view(probe)helper that loopget().await+yield_now()under 5s timeout. matches existingwait_for_message/wait_untilpattern intests/multi_peer_sync.rs.why poll, not subscribe to Notify
subscribe AFTER mutation is racy (Notify may have already fired and dropped). fresh
getcheap. tight loop bounded. simpler.ask/watch buckets considered
issue mention
ask(...)swap. only work when actor reply with()after applying mutation. ALL ten sites already going throughstate::mutate(...).await(which ISask(Mutate)under the hood). so for same-actor reads, nothing to swap — sleep just defensive cargo cult. for derived-actor reads,ask(Get)return cached value not yet recomputed, soaskno help. polling necessary, NOT a fixed wait.ran out of bucket=watch sites — no truly fire-and-forget sleep in this file.
gate
ran raw
cargo(nojustin sandbox):cargo fmt --check— cleancargo clippy --workspace --all-targets -- -D warnings— cleancargo test -p willow-client --lib— 303 pass before, 303 pass aftercargo check --target wasm32-unknown-unknown -p willow-client— cleancargo test --workspace --tests— all greenbefore/after grep
https://claude.ai/code/session_01FHLLfYeh9P9FP7Y47wu9We
Generated by Claude Code