simplify 2
This commit is contained in:
parent
2fcdbfd27d
commit
82929d7479
4 changed files with 503 additions and 1126 deletions
|
|
@ -86,13 +86,11 @@ content/file-manifest version IDs are the event IDs that created them. Rejected
|
|||
requests and retries of accepted requests append no events. Timestamps never
|
||||
determine event ordering.
|
||||
|
||||
The WebSocket handshake retains `lastSeenVaultUpdateId`. The server sends
|
||||
`{type: "vaultEvents", headEventId, events}` in increasing order, including events
|
||||
originated by that client. Each connection drains the database through one sender.
|
||||
In-memory notifications only wake it; polling covers missed wakes and requests
|
||||
interrupted after commit. Reconnect with the last durably processed event ID. The
|
||||
HTTP replay endpoint uses the same history, so losing the final notification is
|
||||
recoverable.
|
||||
WebSocket messages are `{type: "vaultChanged"}` hints. Clients fetch ordered
|
||||
history through HTTP from their last incorporated event ID. The server compares
|
||||
its current history checkpoint after broadcasts and periodically, so missed wakes
|
||||
and requests interrupted after commit still trigger catchup. Reconnecting sends a
|
||||
fresh hint without replaying events over the socket. Cursor messages are unchanged.
|
||||
A fresh client reads `/vault-snapshot`, applies it durably, then replays after its
|
||||
watermark. Successful HTTP responses include `X-Vault-Link-History`, containing
|
||||
an event ID and a random event-incarnation token. Clients persist and echo this
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::app_state::database::models::{DeviceId, DocumentId, EventBatch, VaultUpdateId};
|
||||
use crate::app_state::database::models::{DeviceId, DocumentId, VaultUpdateId};
|
||||
|
||||
#[derive(TS, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WebSocketHandshake {
|
||||
pub token: String,
|
||||
pub device_id: DeviceId,
|
||||
|
||||
#[ts(as = "Option<i32>")]
|
||||
pub last_seen_vault_update_id: Option<VaultUpdateId>,
|
||||
}
|
||||
|
||||
#[derive(TS, Deserialize, Clone, Debug)]
|
||||
|
|
@ -67,6 +64,6 @@ pub enum WebSocketClientMessage {
|
|||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
#[ts(export)]
|
||||
pub enum WebSocketServerMessage {
|
||||
VaultEvents(EventBatch),
|
||||
VaultChanged,
|
||||
CursorPositions(CursorPositionFromServer),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,4 +20,4 @@ pub const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Info;
|
|||
|
||||
pub const DEFAULT_MERGEABLE_FILE_EXTENSIONS: &[&str] = &["md", "txt"];
|
||||
|
||||
pub const SUPPORTED_API_VERSION: u32 = 4;
|
||||
pub const SUPPORTED_API_VERSION: u32 = 5;
|
||||
|
|
|
|||
|
|
@ -69,26 +69,21 @@ async fn websocket(
|
|||
|
||||
let device = authenticated.handshake.device_id;
|
||||
let session = state.cursors.register_connection(&vault, &device).await;
|
||||
let mut after = authenticated
|
||||
.handshake
|
||||
.last_seen_vault_update_id
|
||||
.unwrap_or(0);
|
||||
let mut last_checkpoint = None;
|
||||
let mut notifications = state.broadcasts.get_receiver(vault.clone()).await;
|
||||
let mut timer = tokio::time::interval(Duration::from_secs(2));
|
||||
timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
|
||||
info!("WebSocket connected to vault {vault}");
|
||||
|
||||
// Exactly one sender drains durable events in commit order. In-memory
|
||||
// notifications are only wakeups, so lag and commit/notify crashes are safe.
|
||||
// HTTP owns event replay. Poll the cheap checkpoint to recover missed
|
||||
// broadcasts, including a commit followed by an interrupted notification.
|
||||
let result = async {
|
||||
loop {
|
||||
let batch = state.database.events_after(&vault, after).await.map_err(server_error)?;
|
||||
|
||||
if !batch.events.is_empty() {
|
||||
let head = batch.end_event_id.unwrap_or(batch.head_event_id);
|
||||
send_update_over_websocket(&WebSocketServerMessage::VaultEvents(batch), &mut sender).await?;
|
||||
after = head;
|
||||
let checkpoint = state.database.history_checkpoint(&vault).await.map_err(server_error)?;
|
||||
if last_checkpoint.as_ref() != Some(&checkpoint) {
|
||||
send_update_over_websocket(&WebSocketServerMessage::VaultChanged, &mut sender).await?;
|
||||
last_checkpoint = Some(checkpoint);
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
|
|
@ -99,7 +94,7 @@ async fn websocket(
|
|||
send_update_over_websocket(&WebSocketServerMessage::CursorPositions(positions), &mut sender).await?;
|
||||
},
|
||||
|
||||
Err(RecvError::Lagged(_)) => {}, // Drain the durable log on the next iteration.
|
||||
Err(RecvError::Lagged(_)) => {}, // Check the durable head on the next iteration.
|
||||
Err(RecvError::Closed) => break,
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue