From f029e652a1c2c00f1845f27882902f28d6a2c9f1 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sun, 4 Oct 2026 03:46:18 +0200 Subject: [PATCH] fix(core): keep a folder's pin when the folder lands (B148) A folder pinned before its mkdir landed kept the placeholder uid in pins.uid, which named no node once the landing moved the row to the real uid. The landing now moves the pin with it. --- crates/pdfs-core/src/db/nodes.rs | 7 +++++ crates/pdfs-core/src/db/tests.rs | 47 ++++++++++++++++++++++++++++++++ docs/BUGS.md | 17 ++++++++++++ docs/CHANGELOG.md | 2 ++ 4 files changed, 73 insertions(+) diff --git a/crates/pdfs-core/src/db/nodes.rs b/crates/pdfs-core/src/db/nodes.rs index 766349c..de5c804 100644 --- a/crates/pdfs-core/src/db/nodes.rs +++ b/crates/pdfs-core/src/db/nodes.rs @@ -733,6 +733,13 @@ pub(super) fn adopt_placeholder_row_tx( local: &str, real: &str, ) -> Result<()> { + // A folder pinned before it landed stays pinned: the pin keyed by the + // placeholder was dropped with it (`docs/BUGS.md` B148). + tx.execute( + "UPDATE OR IGNORE pins SET uid = ?2 WHERE uid = ?1", + params![local, real], + )?; + tx.execute("DELETE FROM pins WHERE uid = ?1", params![local])?; // The node a row holds names its parent too, and is what a restart builds // the tree from: a folder that landed with no write to its children after // left them under a parent no row has (`docs/BUGS.md` B140). diff --git a/crates/pdfs-core/src/db/tests.rs b/crates/pdfs-core/src/db/tests.rs index ae4bd6d..af9ad91 100644 --- a/crates/pdfs-core/src/db/tests.rs +++ b/crates/pdfs-core/src/db/tests.rs @@ -3321,6 +3321,53 @@ fn a_landed_folder_keeps_a_row_until_the_server_copy_replaces_it() { assert_eq!(found[0].path, "New folder/inside.txt"); } +/// B148: a folder pinned before it landed is still pinned once it has. +#[test] +fn a_folder_pinned_before_it_landed_stays_pinned() { + let db = Db::open_in_memory().unwrap(); + db.upsert_node(&folder("root", None, "My Files")).unwrap(); + let local = NodeUid::new(VolumeId::from("local"), LinkId::from("dir")); + let mut placeholder = folder("dir", Some("root"), "Keep"); + placeholder.uid = local.clone(); + db.upsert_node(&placeholder).unwrap(); + db.pin_add(&local.to_string(), "Keep", true).unwrap(); + let (id, _) = db + .enqueue_op(&PendingOp { + id: 0, + kind: OP_MKDIR.to_string(), + uid: local.to_string(), + parent_uid: Some(uid("root").to_string()), + name: Some("Keep".to_string()), + blob_path: None, + meta_json: None, + created_at: 1, + attempts: 0, + last_error: None, + next_attempt_at: 0, + }) + .unwrap(); + + db.finish_create( + id, + None, + &landing( + &local.to_string(), + &uid("real").to_string(), + &uid("root").to_string(), + "Keep", + ), + |_| None, + ) + .unwrap(); + + assert!(db.is_pinned(&uid("real").to_string()).unwrap()); + assert!(!db.is_pinned(&local.to_string()).unwrap()); + let pins = db.pin_list().unwrap(); + assert_eq!(pins.len(), 1); + assert_eq!(pins[0].uid, uid("real").to_string()); + assert!(pins[0].recursive); +} + /// A file deleted while its create was on the wire still lands: nothing stops /// an upload in flight. The node it made is unwanted, so a trash of it is /// queued in the same transaction, already claimed by the drain that landed it diff --git a/docs/BUGS.md b/docs/BUGS.md index b8fa298..cb37ba2 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -12,6 +12,23 @@ Conventions: --- +## B148 — A folder pinned before it has reached Drive loses its pin + +**Status:** Fixed (unverified). +**Found:** 2026-10-04, while planning the move of queued ops to local ids: every table keyed by +uid was checked for what happens when a placeholder lands, and `pins` was the one no landing +touched. + +**Where:** `adopt_placeholder_row_tx` in `crates/pdfs-core/src/db/nodes.rs`. + +**Cause.** A folder made through the mount has a placeholder uid (`local~…`) until its mkdir lands. +Pinning it then stored the placeholder in `pins.uid`. The landing moved the node row, the children +and the queued ops to the real uid, but not the pin, which then named no node: the folder was no +longer kept offline and the pin list showed an entry for nothing. + +**Fix.** The landing moves the pin to the real uid in the same transaction. A pin the real uid +already has wins, and the placeholder's is dropped. + ## B147 — A new file made under a name just renamed away can share the first file's node **Status:** Fixed (unverified). diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0ca936e..a1a14e1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -31,6 +31,8 @@ are kept. Queued changes get a column for the reason Drive refused them. for this release. ### Fixed +- **A folder kept offline before it reached Drive stays kept offline.** Its pin was lost when the + folder landed (B148). - **A queued change no longer holds up the queue forever on a link that stops answering.** Sending a new folder, a delete or a file waits at most 20 seconds for Drive, plus time for the file's size, and then the queue treats the link as down and tries again.