Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,36 @@ fn timed(latencies: &mut BTreeMap<&'static str, Vec<f64>>, name: &'static str, r
latencies.entry(name).or_default().push(started.elapsed().as_secs_f64() * 1000.0);
}

/// A chat tab's create journals the rows it changed, not every other chat tab of its pane. The public fold
/// holds what the journal states, decorated (a chat tab's `content_kind` is `conversation`, its `extra`
/// names the agent session and browser profile), and the projection restates a pane's browser tabs raw: a
/// raw tab never equalled its decorated fold value, so every chat tab in the pane was journaled again on
/// each create. With 30 tabs open the person's 31st took the journal 30 rows more, and a busy session's
/// writers queued behind that ("a person's request waited behind the busy sessions", this file's first
/// test, red 9 of 31 full runs).
#[test]
fn a_chat_tab_create_restates_no_other_chat_tab_of_its_pane() {
let session = Session::new("chat-tab-restates");
let mux = session.open();
let sidebar = sidebar(&mux);
let workspace = workspace_slot(&mux, &sidebar.loose[0]);
for index in 0..30 {
open_chat_tab(&mux, workspace, format!("before-{index}"));
}
let revision = mux.with_state(|state| state.resource_revision);
open_chat_tab(&mux, workspace, "the-person".into());
let page = mux.workspace_registry.lock().unwrap().resource_events_after(revision).unwrap();
let tabs: Vec<String> = page
.batches
.iter()
.flat_map(|batch| batch.changes.as_array().cloned().unwrap_or_default())
.filter(|change| change["kind"] == "upsert" && change["resource"] == "tab")
.filter_map(|change| change["id"].as_str().map(str::to_owned))
.collect();
mux.shutdown();
assert!(tabs.len() <= 2, "one chat tab create restated {} tabs: {tabs:?}", tabs.len());
}

/// A chat tab's create validates the tabs it changed, not every tab of its pane: the pane's new order
/// (`SetTabOrder`) moves positions only, which its set and contiguous-position checks cover, as a workspace
/// order change already does. Checking every tab took about 5 queries per tab of the pane under the registry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,31 @@ impl PublicTopologyFold {
}
}

/// Whether decorating `change` could make the fold state it: an upsert of a resource decoration adds to
/// (`state/values.rs` `decorate_value`: a workspace, tab or screen) whose held value differs from it only in
/// what decoration sets (`content_kind`, `extra`). One that differs elsewhere (a tab whose index moved) is
/// kept without a query.
fn may_state_decorated(&self, change: &Value) -> bool {
let (Some(resource), Some(id)) = (change["resource"].as_str(), change["id"].as_str())
else {
return false;
};
if change["kind"] != "upsert" || !matches!(resource, "workspace" | "tab" | "screen") {
return false;
}
let Some(held) = self.values.get(&(resource.to_string(), id.to_string())) else {
return false;
};
let (Some(held), Some(raw)) = (held.as_object(), change["value"].as_object()) else {
return false;
};
let decorated_only = |key: &String| key == "content_kind" || key == "extra";
held.iter()
.filter(|(key, _)| !decorated_only(key))
.all(|(key, value)| raw.get(key) == Some(value))
&& raw.keys().filter(|key| !decorated_only(key)).all(|key| held.contains_key(key))
}

fn states(&self, change: &Value) -> bool {
change["kind"] == "upsert"
&& change["resource"]
Expand Down Expand Up @@ -99,11 +124,27 @@ impl WorkspaceRegistry {
self.catch_up_public_fold();
let Some(fold) = self.public_fold.as_ref() else { return Ok(deltas.clone()) };
let changes = deltas.as_array().context("resource deltas are not an array")?;
// The fold holds decorated values (`record_public_fold`), the projection raw ones: a raw upsert of a
// decorated tab or screen (a chat tab, a frontend browser's profile, a pinned screen) never equals what
// the fold holds, so it is compared decorated as the journal would write it. Without this, every chat
// tab of a pane was journaled again on each create in it.
let connection = self.connection.get();
let decorate = crate::state::store::state_tables_ready(&connection)?;
let mut kept = Vec::with_capacity(changes.len());
for change in changes {
if fold.states(change) {
continue;
}
if decorate && fold.may_state_decorated(change) {
let mut decorated = change.clone();
let resource = change["resource"].as_str().unwrap_or_default();
if let Some(value) = decorated.get_mut("value") {
crate::state::values::decorate_value(&connection, resource, value)?;
}
if fold.states(&decorated) {
continue;
}
}
let mut change = change.clone();
if let Some(object) = change.as_object_mut()
&& object.contains_key("sequence")
Expand Down
Loading