mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 16:56:59 +01:00
fix(fuse): wait for a create on the wire that holds the name (B154); a write whose answer was lost is not a conflict (B155)
B154 is a regression from the B153 fix: after a failure on a held name the op woke the ops waiting for it, but the database cannot see a create on the wire, so the op was due again at once and spent its waits in milliseconds. No wake while another create on the wire holds the name. B155: a revision upload that reached Drive but lost its answer left the file at a revision this daemon never heard of, and the retry kept the write as a conflict copy of itself. A complete write whose bytes the remote holds, by size and SHA-1, is sent again instead. The fake Drive gains hold_reply_to_next_create and lose_reply_to_next_revision for the two new sim tests.
This commit is contained in:
1 parent
7269cb5118
commit
3f74b95b34
6 files changed
+204
-12
No files matched your search
@@ -402,8 +402,12 @@ impl Core {
|
||||
// What held the name wakes the ops waiting for it as it lets go
|
||||
// (`Db::wake_ops_waiting_for`). Letting go between our check and
|
||||
// the record above woke nothing, and the op sat out its backoff.
|
||||
// A create on the wire holds it where the database cannot see:
|
||||
// woken here, the op spent its waits in milliseconds and landed
|
||||
// under a conflict name (`docs/BUGS.md` B154).
|
||||
if held
|
||||
&& let Some(name) = op.name.as_deref()
|
||||
&& !self.creating_named(&op, name)
|
||||
&& let Err(e) = self.db.wake_ops_waiting_for(name)
|
||||
{
|
||||
debug!(uid = %op.uid, error = %e, "waking the ops waiting for a name failed");
|
||||
@@ -1238,6 +1242,15 @@ impl Core {
|
||||
Ok(creating || self.db.has_pending_move_from(name)?)
|
||||
}
|
||||
|
||||
/// Whether a create other than `op` is on the wire under `name`, in any
|
||||
/// folder.
|
||||
fn creating_named(&self, op: &PendingOp, name: &str) -> bool {
|
||||
self.creating
|
||||
.lock()
|
||||
.iter()
|
||||
.any(|(id, (_, n))| *id != op.id && n == name)
|
||||
}
|
||||
|
||||
/// Trash the node a create made after the file was deleted, or replaced by
|
||||
/// a rename, while its upload was on the wire (`docs/BUGS.md` B129).
|
||||
///
|
||||
@@ -1657,6 +1670,7 @@ impl Core {
|
||||
&self,
|
||||
uid: &NodeUid,
|
||||
meta: &StagedWrite,
|
||||
blob: &Path,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let Some(ref base) = meta.based_on else {
|
||||
return Ok(None);
|
||||
@@ -1687,6 +1701,21 @@ impl Core {
|
||||
"queued write chains onto our own sealed revision; not a conflict");
|
||||
return Ok(None);
|
||||
}
|
||||
// An earlier attempt at this write may have landed with its answer
|
||||
// lost: the remote then holds these very bytes in a revision we never
|
||||
// heard of. Forked, the write was a conflict copy of itself
|
||||
// (docs/BUGS.md B155); sent again, it is a revision of the same bytes.
|
||||
if meta.complete
|
||||
&& node_size(&node) == meta.len
|
||||
&& let NodeKind::File {
|
||||
content_sha1: Some(remote),
|
||||
..
|
||||
} = &node.kind
|
||||
&& remote.eq_ignore_ascii_case(&staged_sha1(blob)?)
|
||||
{
|
||||
debug!(%uid, ?remote_rev, "the remote already holds the queued write; not a conflict");
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(reason))
|
||||
}
|
||||
|
||||
@@ -2014,7 +2043,7 @@ impl Core {
|
||||
let meta: StagedWrite = serde_json::from_str(op.meta_json.as_deref().unwrap_or(""))?;
|
||||
let uid = parse_node_uid(&meta.uid).ok_or("staged write has an unparseable uid")?;
|
||||
|
||||
if let Some(reason) = self.revision_conflict(&uid, &meta)? {
|
||||
if let Some(reason) = self.revision_conflict(&uid, &meta, &blob)? {
|
||||
// Trashing the file from the mount drops its queued write, but not
|
||||
// from under a worker already holding it. That worker then finds the
|
||||
// node trashed — by us — and would upload the withdrawn bytes as a
|
||||
|
||||
Reference in new issue
Block a user