fix(messaging): guard handle_send against re-entrancy to prevent dropped sends #211
No reviewers
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_archipelagos!211
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "development_fix_send_reentrancy_race"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Rapid clicks on
.send-btn(or repeated Enter) were spawning N concurrentsend_messagefutures racing on the same client, with most resolutions silently dropped. The existingsending.set(true)ran inside the spawned future, so all clicks completed their synchronous closure body (clone +spawn) before any future was polled — N futures queued before the first one had a chance to flipsending.Button's in-handler click-guard could not rescue this because the prop value the guard reads is only updated on the next render, which itself only happens after the parent flipssending.This PR adds a single synchronous re-entrancy guard at the top of
handle_sendand liftssending.set(true)out of the spawned future so it runs synchronously, beforespawn. The terminalsending.set(false)at the end of the future is unchanged and still covers both Ok and Err.Related Issue
Closes #183
Changes
archipelagos/messaging/src/archipelago.rs: in thehandle_sendclosureif *sending.peek() { return; }as the first statement (no subscription, synchronous read).sending.set(true)from inside the spawned future to beforespawn, inside theif let Route::Chat { sid, .. }branch (so we don't latchsendingwhen the route isn't a chat).sending.set(false)at the end of the future is unchanged.Test Results
cargo test -p hero_archipelagos_messaging --lib: 5/5 passed (the PR #210 tests still green).cargo check -p hero_archipelagos_messaging: clean.cargo check -p hero_archipelagos_messaging --target wasm32-unknown-unknown: clean.cargo check --workspace --exclude hero_archipelagos_messaging: clean (9 pre-existingunused_mutwarnings inserver/src/main.rsare unrelated).Live verified in browser:
make run->http://localhost:8886-> messaging DM. Pasted the issue's 10-click DevTools snippet, observed exactly 1 message bubble appear, F5, re-opened the chat, exactly 1 of 10rapid#Nmessages persisted (vs 0 before the fix). Send button reflected the disabled state during the in-flight RPC. Subsequent single send worked normally.Test plan
btn.click()s -> 1 message persists.Notes
cargo testwould require a Dioxus runtime harness with a controllable executor. A unit test overSignal::set/Signal::peekwould just retest the type's existing semantics.own_message_idsinsert +services::save_own_message_idscalls inside the spawned future run exactly once for the accepted send. No orphan SIDs from dropped clicks.props.on_send-> the gatedhandle_send, so it's covered by the same guard.Out of scope
disabledissue (re-introducing the HTMLdisabledattribute or otherwise hardening Button against synthetic.click()calls during the brief tick before re-render). The parent-level guard is the correct and sufficient fix for #183.