test(sync): audit browser recovery paths
This commit is contained in:
+21
-16
@@ -842,6 +842,7 @@ async fn sync_acknowledgements(
|
||||
.get("reconnect")
|
||||
.filter(|value| !value.is_empty())
|
||||
.cloned();
|
||||
let persistent_stream = reconnect_key.is_some();
|
||||
|
||||
let mut sync = state.sync.lock().unwrap();
|
||||
if let Some(key) = reconnect_key {
|
||||
@@ -921,22 +922,26 @@ async fn sync_acknowledgements(
|
||||
};
|
||||
drop(sync);
|
||||
let heartbeat_interval = state.acknowledgement_heartbeat_interval;
|
||||
let heartbeat = stream::unfold(heartbeat_interval, |interval| async move {
|
||||
tokio::time::sleep(interval).await;
|
||||
Some((
|
||||
Ok(Event::default()
|
||||
.event("heartbeat")
|
||||
.data("{\"status\":\"ok\"}")),
|
||||
interval,
|
||||
))
|
||||
});
|
||||
Ok(
|
||||
Sse::new(stream::iter(events).chain(heartbeat).boxed()).keep_alive(
|
||||
KeepAlive::new()
|
||||
.interval(heartbeat_interval)
|
||||
.text("heartbeat"),
|
||||
),
|
||||
)
|
||||
let event_stream = stream::iter(events).boxed();
|
||||
let response_stream = if persistent_stream {
|
||||
let heartbeat = stream::unfold(heartbeat_interval, |interval| async move {
|
||||
tokio::time::sleep(interval).await;
|
||||
Some((
|
||||
Ok(Event::default()
|
||||
.event("heartbeat")
|
||||
.data("{\"status\":\"ok\"}")),
|
||||
interval,
|
||||
))
|
||||
});
|
||||
event_stream.chain(heartbeat).boxed()
|
||||
} else {
|
||||
event_stream
|
||||
};
|
||||
Ok(Sse::new(response_stream).keep_alive(
|
||||
KeepAlive::new()
|
||||
.interval(heartbeat_interval)
|
||||
.text("heartbeat"),
|
||||
))
|
||||
}
|
||||
|
||||
fn registry(state: Arc<AppState>) -> impl DispatchRegistry {
|
||||
|
||||
@@ -443,6 +443,21 @@ async function continuePendingWork() {
|
||||
setTimeout(() => synchronize(validatePending(commands[0])).catch(failPermanently), 0);
|
||||
}
|
||||
|
||||
async function renewOfflineLease(command) {
|
||||
if (stopped || root.getAttribute("data-sync-phase") !== "offline") return;
|
||||
const lease = await claimUploaderLease(database);
|
||||
publishLease(lease);
|
||||
if (!lease.leader) {
|
||||
setPhase("standby", "Another tab owns sync; waiting for lease takeover.");
|
||||
leaseTimer = setTimeout(() => runLeaseLoop(command).catch(failPermanently), LEASE_POLL_MS);
|
||||
return;
|
||||
}
|
||||
leaseTimer = setTimeout(
|
||||
() => renewOfflineLease(command).catch(failPermanently),
|
||||
LEASE_MS / 2,
|
||||
);
|
||||
}
|
||||
|
||||
async function synchronize(command) {
|
||||
if (synchronizing) return;
|
||||
synchronizing = true;
|
||||
@@ -455,7 +470,10 @@ async function synchronize(command) {
|
||||
}
|
||||
clearTimeout(leaseTimer);
|
||||
leaseTimer = setTimeout(() => {
|
||||
if (!stopped && root.getAttribute("data-sync-phase") !== "acknowledged") {
|
||||
if (stopped || root.getAttribute("data-sync-phase") === "acknowledged") return;
|
||||
if (root.getAttribute("data-sync-phase") === "offline") {
|
||||
renewOfflineLease(command).catch(failPermanently);
|
||||
} else {
|
||||
synchronize(command).catch(failPermanently);
|
||||
}
|
||||
}, LEASE_MS / 2);
|
||||
|
||||
@@ -829,12 +829,16 @@ async fn schema_upgrade_preserves_queued_order_and_local_intent() -> WebDriverRe
|
||||
])
|
||||
);
|
||||
|
||||
driver.find(By::Css("[data-sync-retry]")).await?.click().await?;
|
||||
wait_until(
|
||||
&driver,
|
||||
"const root = document.querySelector('[data-kanban-sync]'); return root?.getAttribute('data-sync-phase') === 'backpressured' && root?.getAttribute('data-sync-pending-count') === '1'",
|
||||
)
|
||||
.await?;
|
||||
for expected_pending in [2, 1] {
|
||||
driver.find(By::Css("[data-sync-retry]")).await?.click().await?;
|
||||
wait_until(
|
||||
&driver,
|
||||
&format!(
|
||||
"const root = document.querySelector('[data-kanban-sync]'); return root?.getAttribute('data-sync-phase') === 'offline' && root?.getAttribute('data-sync-pending-count') === '{expected_pending}'"
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
driver.find(By::Css("[data-sync-retry]")).await?.click().await?;
|
||||
wait_until(
|
||||
&driver,
|
||||
|
||||
Reference in New Issue
Block a user