enable_observation() calls disable_observation() first, dropping the previous
ObservableSqliteDatabase and its broadcast broker. The broker owns the only
broadcast::Sender, so every Receiver handed out earlier gets RecvError::Closed on its
next recv() — the change stream just ends, with no error attributable to a cause and no
notification to whoever set up that subscription.
The crate-level behavior is documented ("the previous observer is disabled first … causing
existing subscriber streams to terminate. Callers must re-subscribe"), but the observe
IPC command exposes it to independent callers who cannot coordinate a re-subscribe.
Observation is effectively per webview window over one shared cached wrapper, so a second
window calling Database.load(key).observe(...) on a key a first window already observes
tears down the first window's broker — the first window's subscribe callback silently
stops firing, with no error to either window.
Repro
Minimal, runnable repro (cargo run, prints an explicit verdict; the subscriber created
after the re-observe still receives its event, proving the write published and the probe is
sound):
https://github.com/yokuze/mcve/tree/23aa4a2acc7e0956ba499751770d4c54b9451da1/cases/004-sqlx-sqlite-toolkit-reobserve-kills-existing-subscribers
Root cause (rev e1b3836)
enable_observation drops the current observer before installing the new one (doc comment states existing streams terminate): wrapper.rs#L390-L402
ObservationBroker owns the sole change_tx: broadcast::Sender: broker.rs#L52-L58
observe command calls remove_for_db + enable_observation, exposing the teardown to independent callers: src/commands.rs#L573-L617
Expected
Re-observing should not silently disconnect existing subscribers — e.g. make
observe/enable_observation idempotent when the requested table set and config are
unchanged (keep the existing broker), support additive/reference-counted observers, or at
minimum deliver a distinguishable close reason so a caller can tell "someone re-observed"
apart from a normal shutdown.
enable_observation()callsdisable_observation()first, dropping the previousObservableSqliteDatabaseand its broadcast broker. The broker owns the onlybroadcast::Sender, so everyReceiverhanded out earlier getsRecvError::Closedon itsnext
recv()— the change stream just ends, with no error attributable to a cause and nonotification to whoever set up that subscription.
The crate-level behavior is documented ("the previous observer is disabled first … causing
existing subscriber streams to terminate. Callers must re-subscribe"), but the
observeIPC command exposes it to independent callers who cannot coordinate a re-subscribe.
Observation is effectively per webview window over one shared cached wrapper, so a second
window calling
Database.load(key).observe(...)on a key a first window already observestears down the first window's broker — the first window's
subscribecallback silentlystops firing, with no error to either window.
Repro
Minimal, runnable repro (
cargo run, prints an explicit verdict; the subscriber createdafter the re-observe still receives its event, proving the write published and the probe is
sound):
https://github.com/yokuze/mcve/tree/23aa4a2acc7e0956ba499751770d4c54b9451da1/cases/004-sqlx-sqlite-toolkit-reobserve-kills-existing-subscribers
Root cause (rev
e1b3836)enable_observationdrops the current observer before installing the new one (doc comment states existing streams terminate):wrapper.rs#L390-L402ObservationBrokerowns the solechange_tx: broadcast::Sender:broker.rs#L52-L58observecommand callsremove_for_db+enable_observation, exposing the teardown to independent callers:src/commands.rs#L573-L617Expected
Re-observing should not silently disconnect existing subscribers — e.g. make
observe/enable_observationidempotent when the requested table set and config areunchanged (keep the existing broker), support additive/reference-counted observers, or at
minimum deliver a distinguishable close reason so a caller can tell "someone re-observed"
apart from a normal shutdown.