Update buffer files when synchronizing buffers

Nathan Sobo created

It's possible that the host was disconnected when attempting to notify
guests of a file save, so we need to transmit this in order to correctly
update the file's mtime.

Next failing seed OPERATIONS=200 SEED=6894

Change summary

crates/collab/src/rpc.rs                                |  4 
crates/collab/src/tests/randomized_integration_tests.rs | 28 +++++++---
crates/db/src/db.rs                                     |  2 
crates/project/src/project.rs                           | 15 +++++
crates/workspace/src/persistence.rs                     |  2 
5 files changed, 35 insertions(+), 16 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -580,7 +580,7 @@ impl Server {
 
             drop(foreground_message_handlers);
             tracing::info!(%user_id, %login, %connection_id, %address, "signing out");
-            if let Err(error) = sign_out(session, teardown, executor).await {
+            if let Err(error) = connection_lost(session, teardown, executor).await {
                 tracing::error!(%user_id, %login, %connection_id, %address, ?error, "error signing out");
             }
 
@@ -781,7 +781,7 @@ pub async fn handle_metrics(Extension(server): Extension<Arc<Server>>) -> Result
 }
 
 #[instrument(err, skip(executor))]
-async fn sign_out(
+async fn connection_lost(
     session: Session,
     mut teardown: watch::Receiver<()>,
     executor: Executor,

crates/collab/src/tests/randomized_integration_tests.rs 🔗

@@ -314,6 +314,7 @@ async fn test_random_collaboration(
                     .read_with(client_cx, |project, _| project.remote_id())
                     .unwrap()
             };
+            let guest_user_id = client.user_id().unwrap();
 
             let host_project = clients.iter().find_map(|(client, cx)| {
                 let project = client.local_projects.iter().find(|host_project| {
@@ -321,14 +322,15 @@ async fn test_random_collaboration(
                         host_project.remote_id() == Some(project_id)
                     })
                 })?;
-                Some((project, cx))
+                Some((client.user_id().unwrap(), project, cx))
             });
 
-            let (host_project, host_cx) = if let Some((host_project, host_cx)) = host_project {
-                (host_project, host_cx)
-            } else {
-                continue;
-            };
+            let (host_user_id, host_project, host_cx) =
+                if let Some((host_user_id, host_project, host_cx)) = host_project {
+                    (host_user_id, host_project, host_cx)
+                } else {
+                    continue;
+                };
 
             for guest_buffer in guest_buffers {
                 let buffer_id = guest_buffer.read_with(client_cx, |buffer, _| buffer.remote_id());
@@ -366,9 +368,17 @@ async fn test_random_collaboration(
                 let guest_file = guest_buffer.read_with(client_cx, |b, _| b.file().cloned());
                 match (host_file, guest_file) {
                     (Some(host_file), Some(guest_file)) => {
-                        assert_eq!(host_file.mtime(), guest_file.mtime());
-                        assert_eq!(host_file.path(), guest_file.path());
-                        assert_eq!(host_file.is_deleted(), guest_file.is_deleted());
+                        assert_eq!(guest_file.path(), host_file.path());
+                        assert_eq!(guest_file.is_deleted(), host_file.is_deleted());
+                        assert_eq!(
+                            guest_file.mtime(),
+                            host_file.mtime(),
+                            "guest {} mtime does not match host {} for path {:?} in project {}",
+                            guest_user_id,
+                            host_user_id,
+                            guest_file.path(),
+                            project_id,
+                        );
                     }
                     (None, None) => {}
                     (None, _) => panic!("host's file is None, guest's isn't "),

crates/db/src/db.rs 🔗

@@ -327,8 +327,6 @@ mod tests {
             .path();
         corrupted_backup_dir.push(DB_FILE_NAME);
 
-        dbg!(&corrupted_backup_dir);
-
         let backup = Connection::open_file(&corrupted_backup_dir.to_string_lossy());
         assert!(backup.select_row::<usize>("SELECT * FROM test").unwrap()()
             .unwrap()

crates/project/src/project.rs 🔗

@@ -359,7 +359,7 @@ impl FormatTrigger {
 impl Project {
     pub fn init(client: &Arc<Client>) {
         client.add_model_message_handler(Self::handle_add_collaborator);
-        client.add_model_message_handler(Self::handle_update_collaborator);
+        client.add_model_message_handler(Self::handle_update_project_collaborator);
         client.add_model_message_handler(Self::handle_remove_collaborator);
         client.add_model_message_handler(Self::handle_buffer_reloaded);
         client.add_model_message_handler(Self::handle_buffer_saved);
@@ -4617,7 +4617,7 @@ impl Project {
         Ok(())
     }
 
-    async fn handle_update_collaborator(
+    async fn handle_update_project_collaborator(
         this: ModelHandle<Self>,
         envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
         _: Arc<Client>,
@@ -5184,9 +5184,20 @@ impl Project {
 
                     let operations = buffer.serialize_ops(Some(remote_version), cx);
                     let client = this.client.clone();
+                    let file = buffer.file().cloned();
                     cx.background()
                         .spawn(
                             async move {
+                                if let Some(file) = file {
+                                    client
+                                        .send(proto::UpdateBufferFile {
+                                            project_id,
+                                            buffer_id: buffer_id as u64,
+                                            file: Some(file.to_proto()),
+                                        })
+                                        .log_err();
+                                }
+
                                 let operations = operations.await;
                                 for chunk in split_operations(operations) {
                                     client

crates/workspace/src/persistence.rs 🔗

@@ -216,7 +216,7 @@ impl WorkspaceDb {
         let mut result = Vec::new();
         let mut delete_tasks = Vec::new();
         for (id, location) in self.recent_workspaces()? {
-            if location.paths().iter().all(|path| dbg!(path).exists()) {
+            if location.paths().iter().all(|path| path.exists()) {
                 result.push((id, location));
             } else {
                 delete_tasks.push(self.delete_stale_workspace(id));