fix: fallback tree live events to polling

This commit is contained in:
lix-2026
2026-05-25 04:38:23 +08:00
parent aa293fec3f
commit 3d338fdde3
2 changed files with 90 additions and 7 deletions
@@ -17,8 +17,9 @@ use serde_json::{json, Value};
use std::convert::Infallible;
use std::path::PathBuf;
use std::pin::Pin;
use std::time::{SystemTime, UNIX_EPOCH};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::broadcast::error::RecvError;
use tokio::time::{interval, MissedTickBehavior};
type BoxedEventStream =
Pin<Box<dyn futures_util::Stream<Item = Result<SseEvent, Infallible>> + Send>>;
@@ -123,11 +124,6 @@ async fn build_tree_live_stream(
canonical_root: PathBuf,
root_uri: String,
) -> Result<(HeaderMap, BoxedEventStream), WebError> {
let subscription = state
.local_folder_watcher_registry()
.subscribe(&canonical_root)
.map_err(|error| WebError::internal(error).with_context(&context))?;
let workspace_id = local_workspace_id_from_root_uri(&root_uri)
.map_err(|error| error.with_context(&context))?;
@@ -148,6 +144,27 @@ async fn build_tree_live_stream(
&file_tree_snapshot,
);
let subscription = match state
.local_folder_watcher_registry()
.subscribe(&canonical_root)
{
Ok(subscription) => subscription,
Err(error) => {
tracing::warn!(
error = %error,
root_uri = %root_uri,
"local_folder tree live watcher unavailable; falling back to revision polling"
);
let stream = build_tree_live_polling_stream(
root_uri,
workspace_id,
revision.revision,
initial_payload,
);
return Ok((HeaderMap::new(), stream));
}
};
let stream = stream::unfold(
(Some(initial_payload), subscription, root_uri, workspace_id),
|(payload, mut subscription, root_uri, workspace_id)| async move {
@@ -184,6 +201,67 @@ async fn build_tree_live_stream(
Ok((HeaderMap::new(), stream))
}
fn build_tree_live_polling_stream(
root_uri: String,
workspace_id: String,
initial_revision: String,
initial_payload: Value,
) -> BoxedEventStream {
let mut poll_interval = interval(Duration::from_millis(1_200));
poll_interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
stream::unfold(
Some(TreeLivePollingState {
root_uri,
workspace_id,
current_revision: initial_revision,
initial_payload: Some(initial_payload),
poll_interval,
}),
|state| async move {
let mut state = state?;
if let Some(payload) = state.initial_payload.take() {
return Some((Ok(stream_event("snapshot", &payload)), Some(state)));
}
loop {
state.poll_interval.tick().await;
let Some((next_revision, resync_payload)) = tree_live_polling_resync_payload(
&state.root_uri,
&state.workspace_id,
&state.current_revision,
) else {
continue;
};
state.current_revision = next_revision;
return Some((Ok(stream_event("resync", &resync_payload)), Some(state)));
}
},
)
.boxed()
}
struct TreeLivePollingState {
root_uri: String,
workspace_id: String,
current_revision: String,
initial_payload: Option<Value>,
poll_interval: tokio::time::Interval,
}
fn tree_live_polling_resync_payload(
root_uri: &str,
workspace_id: &str,
current_revision: &str,
) -> Option<(String, Value)> {
let next_revision = local_folder_watch_revision(root_uri).ok()?;
if next_revision.revision == current_revision {
return None;
}
let resync_payload = rebuild_tree_resync_payload(root_uri, workspace_id)?;
Some((next_revision.revision, resync_payload))
}
fn rebuild_tree_resync_payload(root_uri: &str, workspace_id: &str) -> Option<Value> {
let revision = local_folder_watch_revision(root_uri).ok()?;
let sidebar_snapshot = load_local_folder_page_tree_snapshot(root_uri).ok()?;