fix(sessions): share the write-guard filter and trim repeated walks

prune_sessions hand-rolled the same "guarded by a live lease/lock"
comprehension as the new _guarded_ids helper, so the two could drift on
the next guard change. Move _guarded_ids next to _write_guards_reject in
the maintenance mixin and have prune call it.

delete_session walked the delegate tree up to three times in one write
transaction; compute the target ids once for both the guard check and the
expected-ids fence. delete_sessions ran a per-root guard walk for every
selected id; do one batched check over all roots and their children first
and only attribute per root when something is actually guarded.

The CLI export --delete message repeated "session 'X'" because the
exception text already names the session.
This commit is contained in:
kshitijk4poor
2026-09-27 16:34:22 +05:30
committed by kshitij
parent e78e7ccdeb
commit 230f89b47b
3 changed files with 21 additions and 19 deletions

View File

@@ -1576,16 +1576,16 @@ class SessionSessionsMixin:
def _do(conn):
if conn.execute("SELECT 1 FROM sessions WHERE id = ? LIMIT 1", (session_id,)).fetchone() is None:
return False
if exclude_active_write_guards and self._guarded_ids(
conn, [session_id, *_collect_delegate_child_ids(conn, [session_id])],
):
target_ids = (
[session_id, *_collect_delegate_child_ids(conn, [session_id])]
if exclude_active_write_guards or expected_ids is not None else None
)
if exclude_active_write_guards and self._guarded_ids(conn, target_ids):
# Delegate children cascade with the root, so a guard on any of them refuses too.
raise SessionActiveWriteGuardError(
f"session '{session_id}' (or a delegate child) has an active turn lease or compression lock"
)
if expected_ids is not None and expected_ids != {
session_id, *_collect_delegate_child_ids(conn, [session_id])
}:
if expected_ids is not None and expected_ids != set(target_ids):
return False
if expected_display_messages is not None and any(
self._display_messages_from_conn(conn, covered_id) != expected
@@ -1633,11 +1633,6 @@ class SessionSessionsMixin:
self._remove_session_files(sessions_dir, session_id)
return deleted
def _guarded_ids(self, conn, ids: List[str]) -> set:
"""Ids in *ids* protected by a live turn lease / compression lock. Idle compression-ended
parents are closed, not live, so they are not guarded (matches prune)."""
return {sid for sid in ids if self._write_guards_reject(conn, sid, allow_closed_compression_parent=True)}
def delete_sessions(
self, session_ids: List[str], sessions_dir: Optional[Path] = None,
exclude_active_write_guards: bool = False, skipped_ids: Optional[List[str]] = None,
@@ -1659,10 +1654,13 @@ class SessionSessionsMixin:
if exclude_active_write_guards:
# A root is skipped when it or any delegate child it would cascade is guarded, so the
# cascade below never deletes a guarded row reported back as kept.
active_ids = {
sid for sid in existing
if self._guarded_ids(conn, [sid, *_collect_delegate_child_ids(conn, [sid])])
}
# One batched check first; per-root attribution only when something is guarded.
active_ids: set = set()
if self._guarded_ids(conn, [*existing, *_collect_delegate_child_ids(conn, existing)]):
active_ids = {
sid for sid in existing
if self._guarded_ids(conn, [sid, *_collect_delegate_child_ids(conn, [sid])])
}
existing = [sid for sid in existing if sid not in active_ids]
if skipped_ids is not None:
skipped_ids.extend(sorted(active_ids))