Add remote worktree to project before it is fully deserialized

This prevents a race condition where the host will send us messages and
responses about a worktree that we have seen but haven't yet finished loading.
This commit is contained in:
Antonio Scandurra 2022-01-24 14:00:38 +01:00
parent 245490f934
commit a762f575f4
2 changed files with 82 additions and 80 deletions

View file

@ -247,8 +247,10 @@ impl Project {
let mut worktrees = Vec::new(); let mut worktrees = Vec::new();
for worktree in response.worktrees { for worktree in response.worktrees {
worktrees let (worktree, load_task) = cx
.push(Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx).await?); .update(|cx| Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx));
worktrees.push(worktree);
load_task.detach();
} }
let user_ids = response let user_ids = response
@ -1464,16 +1466,9 @@ impl Project {
.payload .payload
.worktree .worktree
.ok_or_else(|| anyhow!("invalid worktree"))?; .ok_or_else(|| anyhow!("invalid worktree"))?;
cx.spawn(|this, mut cx| { let (worktree, load_task) = Worktree::remote(remote_id, replica_id, worktree, client, cx);
async move { self.add_worktree(&worktree, cx);
let worktree = load_task.detach();
Worktree::remote(remote_id, replica_id, worktree, client, &mut cx).await?;
this.update(&mut cx, |this, cx| this.add_worktree(&worktree, cx));
Ok(())
}
.log_err()
})
.detach();
Ok(()) Ok(())
} }
@ -2551,15 +2546,16 @@ mod tests {
// Create a remote copy of this worktree. // Create a remote copy of this worktree.
let initial_snapshot = tree.read_with(&cx, |tree, _| tree.snapshot()); let initial_snapshot = tree.read_with(&cx, |tree, _| tree.snapshot());
let remote = Worktree::remote( let (remote, load_task) = cx.update(|cx| {
Worktree::remote(
1, 1,
1, 1,
initial_snapshot.to_proto(&Default::default(), Default::default()), initial_snapshot.to_proto(&Default::default(), Default::default()),
rpc.clone(), rpc.clone(),
&mut cx.to_async(), cx,
) )
.await });
.unwrap(); load_task.await;
cx.read(|cx| { cx.read(|cx| {
assert!(!buffer2.read(cx).is_dirty()); assert!(!buffer2.read(cx).is_dirty());

View file

@ -190,13 +190,13 @@ impl Worktree {
Ok(tree) Ok(tree)
} }
pub async fn remote( pub fn remote(
project_remote_id: u64, project_remote_id: u64,
replica_id: ReplicaId, replica_id: ReplicaId,
worktree: proto::Worktree, worktree: proto::Worktree,
client: Arc<Client>, client: Arc<Client>,
cx: &mut AsyncAppContext, cx: &mut MutableAppContext,
) -> Result<ModelHandle<Self>> { ) -> (ModelHandle<Self>, Task<()>) {
let remote_id = worktree.id; let remote_id = worktree.id;
let root_char_bag: CharBag = worktree let root_char_bag: CharBag = worktree
.root_name .root_name
@ -205,7 +205,46 @@ impl Worktree {
.collect(); .collect();
let root_name = worktree.root_name.clone(); let root_name = worktree.root_name.clone();
let weak = worktree.weak; let weak = worktree.weak;
let (entries_by_path, entries_by_id, diagnostic_summaries) = cx let snapshot = Snapshot {
id: WorktreeId(remote_id as usize),
root_name,
root_char_bag,
entries_by_path: Default::default(),
entries_by_id: Default::default(),
};
let (updates_tx, mut updates_rx) = postage::mpsc::channel(64);
let (mut snapshot_tx, snapshot_rx) = watch::channel_with(snapshot.clone());
let worktree_handle = cx.add_model(|_: &mut ModelContext<Worktree>| {
Worktree::Remote(RemoteWorktree {
project_id: project_remote_id,
replica_id,
snapshot: snapshot.clone(),
snapshot_rx: snapshot_rx.clone(),
updates_tx,
client: client.clone(),
queued_operations: Default::default(),
diagnostic_summaries: TreeMap::from_ordered_entries(
worktree.diagnostic_summaries.into_iter().map(|summary| {
(
PathKey(PathBuf::from(summary.path).into()),
DiagnosticSummary {
error_count: summary.error_count as usize,
warning_count: summary.warning_count as usize,
info_count: summary.info_count as usize,
hint_count: summary.hint_count as usize,
},
)
}),
),
weak,
})
});
let deserialize_task = cx.spawn({
let worktree_handle = worktree_handle.clone();
|cx| async move {
let (entries_by_path, entries_by_id) = cx
.background() .background()
.spawn(async move { .spawn(async move {
let mut entries_by_path_edits = Vec::new(); let mut entries_by_path_edits = Vec::new();
@ -230,36 +269,15 @@ impl Worktree {
entries_by_path.edit(entries_by_path_edits, &()); entries_by_path.edit(entries_by_path_edits, &());
entries_by_id.edit(entries_by_id_edits, &()); entries_by_id.edit(entries_by_id_edits, &());
let diagnostic_summaries = TreeMap::from_ordered_entries( (entries_by_path, entries_by_id)
worktree.diagnostic_summaries.into_iter().map(|summary| {
(
PathKey(PathBuf::from(summary.path).into()),
DiagnosticSummary {
error_count: summary.error_count as usize,
warning_count: summary.warning_count as usize,
info_count: summary.info_count as usize,
hint_count: summary.hint_count as usize,
},
)
}),
);
(entries_by_path, entries_by_id, diagnostic_summaries)
}) })
.await; .await;
let worktree = cx.update(|cx| { {
cx.add_model(|cx: &mut ModelContext<Worktree>| { let mut snapshot = snapshot_tx.borrow_mut();
let snapshot = Snapshot { snapshot.entries_by_path = entries_by_path;
id: WorktreeId(remote_id as usize), snapshot.entries_by_id = entries_by_id;
root_name, }
root_char_bag,
entries_by_path,
entries_by_id,
};
let (updates_tx, mut updates_rx) = postage::mpsc::channel(64);
let (mut snapshot_tx, snapshot_rx) = watch::channel_with(snapshot.clone());
cx.background() cx.background()
.spawn(async move { .spawn(async move {
@ -275,7 +293,8 @@ impl Worktree {
{ {
let mut snapshot_rx = snapshot_rx.clone(); let mut snapshot_rx = snapshot_rx.clone();
cx.spawn_weak(|this, mut cx| async move { let this = worktree_handle.downgrade();
cx.spawn(|mut cx| async move {
while let Some(_) = snapshot_rx.recv().await { while let Some(_) = snapshot_rx.recv().await {
if let Some(this) = cx.read(|cx| this.upgrade(cx)) { if let Some(this) = cx.read(|cx| this.upgrade(cx)) {
this.update(&mut cx, |this, cx| this.poll_snapshot(cx)); this.update(&mut cx, |this, cx| this.poll_snapshot(cx));
@ -286,22 +305,9 @@ impl Worktree {
}) })
.detach(); .detach();
} }
}
Worktree::Remote(RemoteWorktree {
project_id: project_remote_id,
replica_id,
snapshot,
snapshot_rx,
updates_tx,
client: client.clone(),
queued_operations: Default::default(),
diagnostic_summaries,
weak,
})
})
}); });
(worktree_handle, deserialize_task)
Ok(worktree)
} }
pub fn as_local(&self) -> Option<&LocalWorktree> { pub fn as_local(&self) -> Option<&LocalWorktree> {