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.

Change summary

crates/workspace/src/multi_workspace.rs | 10 ++++++++++
1 file changed, 10 insertions(+)

Detailed changes

crates/workspace/src/multi_workspace.rs 🔗

@@ -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());