Make reconnects smoother for dev servers (#12223)

Conrad Irwin and Nathan created

Release Notes:

- N/A

Co-authored-by: Nathan <nathan@zed.dev>

Change summary

crates/collab/src/rpc.rs        | 31 +++++++++++++++++++++++--------
crates/headless/src/headless.rs |  5 ++++-
crates/rpc/proto/zed.proto      |  1 +
3 files changed, 28 insertions(+), 9 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -1140,8 +1140,17 @@ impl Server {
             Principal::DevServer(dev_server) => {
                 {
                     let mut pool = self.connection_pool.lock();
-                    if pool.dev_server_connection_id(dev_server.id).is_some() {
-                        return Err(anyhow!(ErrorCode::DevServerAlreadyOnline))?;
+                    if let Some(stale_connection_id) = pool.dev_server_connection_id(dev_server.id)
+                    {
+                        self.peer.send(
+                            stale_connection_id,
+                            proto::ShutdownDevServer {
+                                reason: Some(
+                                    "another dev server connected with the same token".to_string(),
+                                ),
+                            },
+                        )?;
+                        pool.remove_connection(stale_connection_id)?;
                     };
                     pool.add_dev_server(connection_id, dev_server.id, zed_version);
                 }
@@ -2398,9 +2407,12 @@ async fn regenerate_dev_server_token(
         .dev_server_connection_id(dev_server_id);
     if let Some(connection_id) = connection_id {
         shutdown_dev_server_internal(dev_server_id, connection_id, &session).await?;
-        session
-            .peer
-            .send(connection_id, proto::ShutdownDevServer {})?;
+        session.peer.send(
+            connection_id,
+            proto::ShutdownDevServer {
+                reason: Some("dev server token was regenerated".to_string()),
+            },
+        )?;
         let _ = remove_dev_server_connection(dev_server_id, &session).await;
     }
 
@@ -2470,9 +2482,12 @@ async fn delete_dev_server(
         .dev_server_connection_id(dev_server_id);
     if let Some(connection_id) = connection_id {
         shutdown_dev_server_internal(dev_server_id, connection_id, &session).await?;
-        session
-            .peer
-            .send(connection_id, proto::ShutdownDevServer {})?;
+        session.peer.send(
+            connection_id,
+            proto::ShutdownDevServer {
+                reason: Some("dev server was deleted".to_string()),
+            },
+        )?;
         let _ = remove_dev_server_connection(dev_server_id, &session).await;
     }
 

crates/headless/src/headless.rs 🔗

@@ -104,7 +104,10 @@ impl DevServer {
         let request = if self.remote_shutdown {
             None
         } else {
-            Some(self.client.request(proto::ShutdownDevServer {}))
+            Some(
+                self.client
+                    .request(proto::ShutdownDevServer { reason: None }),
+            )
         };
         async move {
             if let Some(request) = request {

crates/rpc/proto/zed.proto 🔗

@@ -510,6 +510,7 @@ message CreateDevServerResponse {
 }
 
 message ShutdownDevServer {
+    optional string reason = 1;
 }
 
 message RenameDevServer {