fix(core): a file deleted after its failed create was written again leaves Drive (B191)

This commit is contained in:
nvrl committed 2026-10-05 04:50:02 +02:00
1 parent 1436c5f37e
commit 57a825887c
4 files changed
+71 -3

No files matched your search

+6 -1
View File
@@ -107,6 +107,10 @@ pub fn op_supersedes(kind: &str) -> bool {
/// name and bytes (`docs/BUGS.md` B151). Such a trash keeps the create's
/// parent and blob, which the drain looks for the node by, and which it
/// discards once the trash is done ([`Db::complete_trash_op`]).
///
/// Sent is where it was sent to ([`Db::note_create_target`]), not only its
/// attempts: a write after the failure clears those for a prompt retry, and
/// the delete then dropped the create whole (`docs/BUGS.md` B191).
fn drop_doomed_ops(tx: &rusqlite::Transaction<'_>, uid: &str) -> Result<Vec<String>> {
const DOOMED: &str = "
WITH RECURSIVE
@@ -134,7 +138,8 @@ fn drop_doomed_ops(tx: &rusqlite::Transaction<'_>, uid: &str) -> Result<Vec<Stri
OR (kind = 'revision'
AND (uid IN (SELECT uid FROM below) OR lid IN (SELECT lid FROM below)))
)";
const SENT: &str = "kind IN ('create', 'mkdir') AND (claimed_at <> 0 OR attempts > 0)";
const SENT: &str = "kind IN ('create', 'mkdir')
AND (claimed_at <> 0 OR attempts > 0 OR sent_to IS NOT NULL)";
let lid = node_lid(tx, uid)?;
let blobs: Vec<String> = {
let mut stmt = tx.prepare(&format!(
+42
View File
@@ -3951,6 +3951,48 @@ fn what_a_create_sent_outlives_a_newer_blob_its_trash_and_a_restart() {
let _ = std::fs::remove_file(path);
}
/// A write after a create failed clears its attempts, so the next try goes
/// out at once. The create was still sent: deleting the file then left no
/// trash, and the file a lost answer made stayed on Drive under the name it
/// was sent with (`docs/BUGS.md` B191).
#[test]
fn a_create_rewritten_after_it_failed_still_leaves_a_trash_when_deleted() {
let db = Db::open_in_memory().unwrap();
let parent = uid("parent").to_string();
let (id, _) = db
.enqueue_op(&PendingOp {
id: 0,
kind: OP_CREATE.to_string(),
uid: "local~sent".to_string(),
parent_uid: Some(parent.clone()),
name: Some("a.txt".to_string()),
blob_path: Some("/staging/first".to_string()),
meta_json: Some("{}".to_string()),
created_at: 1,
attempts: 0,
last_error: None,
next_attempt_at: 0,
})
.unwrap();
db.note_create_target(id, &parent, "a.txt").unwrap();
db.note_create_sent(id, "aaaa").unwrap();
db.record_op_failure(id, "no answer", 0).unwrap();
db.attach_blob_to_create("local~sent", "/staging/second", "{}")
.unwrap()
.expect("the create is still queued");
assert!(db.delete_ops_for_uid("local~sent").unwrap().is_empty());
let ops = db.pending_ops().unwrap();
assert_eq!(ops.len(), 1);
assert_eq!((ops[0].id, ops[0].kind.as_str()), (id, OP_TRASH));
assert_eq!(ops[0].blob_path.as_deref(), Some("/staging/second"));
assert_eq!(
db.create_targets(id).unwrap(),
vec![(parent.clone(), "a.txt".to_string())]
);
}
/// A create deleted after it was sent becomes a trash in its place. The
/// create's backoff, and the failure of an attempt still on the wire, are the
/// create's: the trash is due at once (`docs/BUGS.md` B171).