Fix removed workspace resurrecting via serialization race (#52035)
Richard Feldman
created
In `remove_workspace`, the removed `Entity<Workspace>` could still have
a pending `serialize_workspace` throttle timer (200ms). When that timer
fired, `serialize_workspace_internal` would write the old `session_id`
back to the DB — undoing the removal. On next restart, the workspace
would reappear.
The race window opens whenever any state change (worktree change,
breakpoint change, etc.) triggers `serialize_workspace` within 200ms
before `remove_workspace` is called.
**Fix**: Before the DB cleanup task, `update` the removed workspace
entity to:
1. `session_id.take()` — so any in-flight serialization writes
`session_id: None`
2. `_schedule_serialize_workspace.take()` — cancel the pending throttle
timer
3. `_serialize_workspace_task.take()` — cancel any actively running
serialization task
This mirrors what `remove_from_session` already does (clearing
`session_id`), but `remove_workspace` was missing it.
Release Notes:
- Fixed a bug where a removed workspace could reappear on next launch
due to a serialization race.
@@ -649,6 +649,16 @@ impl MultiWorkspace {
self.active_workspace_index -= 1;
}
+ // Clear session_id and cancel any in-flight serialization on the
+ // removed workspace. Without this, a pending throttle timer from
+ // `serialize_workspace` could fire and write the old session_id
+ // back to the DB, resurrecting the workspace on next launch.
+ removed_workspace.update(cx, |workspace, _cx| {
+ workspace.session_id.take();
+ workspace._schedule_serialize_workspace.take();
+ workspace._serialize_workspace_task.take();
+ });
+
if let Some(workspace_id) = removed_workspace.read(cx).database_id() {
let db = crate::persistence::WorkspaceDb::global(cx);
self.pending_removal_tasks.retain(|task| !task.is_ready());