mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
fix(core,fuse): a node written under its stand-in after landing updates its landed row
This commit is contained in:
1 parent
9c93860cd8
commit
dbc4d9a8c5
4 files changed
+66
-30
No files matched your search
@@ -900,8 +900,25 @@ fn adopt_children_tx(tx: &Transaction<'_>, uid: &str, lid: i64) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn upsert_node_tx(tx: &Transaction<'_>, node: &Node) -> Result<()> {
|
fn upsert_node_tx(tx: &Transaction<'_>, node: &Node) -> Result<()> {
|
||||||
let json = serde_json::to_string(node)?;
|
|
||||||
let uid = node.uid.to_string();
|
let uid = node.uid.to_string();
|
||||||
|
// A node written under its stand-in after it landed is its row, which has
|
||||||
|
// the uid Drive gave it by now: written under the stand-in, it would bring
|
||||||
|
// the placeholder row back beside it.
|
||||||
|
if let Some(lid) = local_lid(&uid) {
|
||||||
|
let landed: Option<String> = tx
|
||||||
|
.query_row(
|
||||||
|
"SELECT uid FROM nodes WHERE lid = ?1 AND uid IS NOT ?2",
|
||||||
|
params![lid, uid],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.optional()?;
|
||||||
|
if let Some(real) = landed.as_deref().and_then(parse_node_uid) {
|
||||||
|
let mut node = node.clone();
|
||||||
|
node.uid = real;
|
||||||
|
return upsert_node_tx(tx, &node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let json = serde_json::to_string(node)?;
|
||||||
let parent_uid = node.parent_uid.as_ref().map(|u| u.to_string());
|
let parent_uid = node.parent_uid.as_ref().map(|u| u.to_string());
|
||||||
|
|
||||||
let prior: Option<PriorRow> = tx
|
let prior: Option<PriorRow> = tx
|
||||||
|
|||||||
@@ -3964,6 +3964,43 @@ fn a_landed_folder_rewrites_no_row_or_op_below_it() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A node the mount still holds under its stand-in, written after it landed,
|
||||||
|
/// updates the landed row: written under the stand-in, it brought the
|
||||||
|
/// placeholder row back beside it.
|
||||||
|
#[test]
|
||||||
|
fn a_stand_in_written_after_landing_updates_the_landed_row() {
|
||||||
|
let db = Db::open_in_memory().unwrap();
|
||||||
|
db.upsert_node(&folder("root", None, "My Files")).unwrap();
|
||||||
|
let lid = db
|
||||||
|
.add_local_row(&uid("root").to_string(), "a.txt", false, 1)
|
||||||
|
.unwrap();
|
||||||
|
let local = local_uid(lid);
|
||||||
|
let mut node = file("x", "root", "a.txt", 3);
|
||||||
|
node.uid = NodeUid::new(VolumeId::from("local"), LinkId::from(lid.to_string()));
|
||||||
|
db.upsert_node(&node).unwrap();
|
||||||
|
db.land_placeholder_row(&local, &uid("a").to_string())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
node.name = "b.txt".into();
|
||||||
|
db.upsert_node(&node).unwrap();
|
||||||
|
|
||||||
|
let rows: Vec<(i64, String, String)> = {
|
||||||
|
let conn = db.conn.lock();
|
||||||
|
let mut stmt = conn
|
||||||
|
.prepare("SELECT lid, uid, name FROM nodes WHERE uid != ?1")
|
||||||
|
.unwrap();
|
||||||
|
stmt.query_map([uid("root").to_string()], |r| {
|
||||||
|
Ok((r.get(0)?, r.get(1)?, r.get(2)?))
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.collect::<rusqlite::Result<_>>()
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
assert_eq!(rows, [(lid, uid("a").to_string(), "b.txt".to_string())]);
|
||||||
|
let landed = db.node_by_uid(&uid("a").to_string()).unwrap().unwrap();
|
||||||
|
assert_eq!((landed.uid, landed.name), (uid("a"), "b.txt".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
/// A create queued inside a folder made here is sent under the folder's real
|
/// A create queued inside a folder made here is sent under the folder's real
|
||||||
/// uid once it lands. Landing where it was sent is no move.
|
/// uid once it lands. Landing where it was sent is no move.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -169,10 +169,6 @@ const DRAIN_BACKOFF_MAX: Duration = Duration::from_secs(300);
|
|||||||
/// directly on a new write or a reconnect, so this only bounds how late a
|
/// directly on a new write or a reconnect, so this only bounds how late a
|
||||||
/// backoff can fire.
|
/// backoff can fire.
|
||||||
const DRAIN_IDLE_POLL: Duration = Duration::from_secs(30);
|
const DRAIN_IDLE_POLL: Duration = Duration::from_secs(30);
|
||||||
/// How long a write whose create just landed waits for the drain to repoint
|
|
||||||
/// its inode (`Core::landed_uid`): 200 polls of 10 ms, two seconds in all.
|
|
||||||
const LANDED_UID_POLLS: u32 = 200;
|
|
||||||
const LANDED_UID_POLL: Duration = Duration::from_millis(10);
|
|
||||||
/// How long the mount waits before asking again for a node it has just created
|
/// How long the mount waits before asking again for a node it has just created
|
||||||
/// when Drive does not list it yet (`Core::fetch_minted_node`).
|
/// when Drive does not list it yet (`Core::fetch_minted_node`).
|
||||||
const MINTED_READ_BACK_DELAYS: [Duration; 2] = [Duration::from_millis(250), Duration::from_secs(1)];
|
const MINTED_READ_BACK_DELAYS: [Duration; 2] = [Duration::from_millis(250), Duration::from_secs(1)];
|
||||||
@@ -3430,9 +3426,9 @@ impl Core {
|
|||||||
break 'queued attached.superseded;
|
break 'queued attached.superseded;
|
||||||
}
|
}
|
||||||
drop(pending);
|
drop(pending);
|
||||||
// The create landed while this handle was open. Its uid is the
|
// The create landed while this handle was open, and its row
|
||||||
// one the drain has just given the inode.
|
// already has the uid Drive gave it.
|
||||||
let Some(real) = self.landed_uid(ino) else {
|
let Some(real) = self.landed_row_uid(ino) else {
|
||||||
error!(%uid, staged = %path.display(),
|
error!(%uid, staged = %path.display(),
|
||||||
"queued create vanished under a write; bytes kept in staging");
|
"queued create vanished under a write; bytes kept in staging");
|
||||||
return Err(Errno::EIO);
|
return Err(Errno::EIO);
|
||||||
@@ -3511,23 +3507,6 @@ impl Core {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The real uid the drain gave `ino` when its queued create landed.
|
|
||||||
///
|
|
||||||
/// Called by a write that found the create already retired. The drain
|
|
||||||
/// repoints the inode right after it drops `pending`, with no network in
|
|
||||||
/// between, so a short wait covers the gap; `None` means the inode is gone
|
|
||||||
/// or never stopped being a placeholder.
|
|
||||||
fn landed_uid(&self, ino: u64) -> Option<NodeUid> {
|
|
||||||
for _ in 0..LANDED_UID_POLLS {
|
|
||||||
let uid = self.state().entries.get(&ino).map(|e| e.uid.clone())?;
|
|
||||||
if !is_local_uid(&uid) {
|
|
||||||
return Some(uid);
|
|
||||||
}
|
|
||||||
std::thread::sleep(LANDED_UID_POLL);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queue the new content of a path-based truncate — `> file`, or any
|
/// Queue the new content of a path-based truncate — `> file`, or any
|
||||||
/// `setattr(size=…)` arriving without a write handle.
|
/// `setattr(size=…)` arriving without a write handle.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
|||||||
places each node under its parent's row as it is now.
|
places each node under its parent's row as it is now.
|
||||||
- [x] Queries over children, subtrees and ancestors by `parent_lid`, and no rewrite of the
|
- [x] Queries over children, subtrees and ancestors by `parent_lid`, and no rewrite of the
|
||||||
children's `parent_uid` when a folder lands (R3), nor of the ops made inside it (R2).
|
children's `parent_uid` when a folder lands (R3), nor of the ops made inside it (R2).
|
||||||
- [ ] No more placeholders: a node made here goes by its local id until it lands. In four
|
- [x] No more placeholders: a node made here goes by its local id until it lands. In four
|
||||||
parts:
|
parts:
|
||||||
- [x] 6a: a node made here goes by `local~<lid>`, a function of its row, instead of a
|
- [x] 6a: a node made here goes by `local~<lid>`, a function of its row, instead of a
|
||||||
minted `local~<ms>-<n>`; schema 41 rewrites the old placeholders (nodes, ops, pins).
|
minted `local~<ms>-<n>`; schema 41 rewrites the old placeholders (nodes, ops, pins).
|
||||||
@@ -48,10 +48,13 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
|||||||
now. The row keeps its stand-in in `nodes.uid` rather than `NULL`: it is as stable,
|
now. The row keeps its stand-in in `nodes.uid` rather than `NULL`: it is as stable,
|
||||||
and `NULL` would have changed some 40 node queries for no change in behaviour. Pins
|
and `NULL` would have changed some 40 node queries for no change in behaviour. Pins
|
||||||
stay keyed by uid and move with the row in the landing transaction (B148).
|
stay keyed by uid and move with the row in the landing transaction (B148).
|
||||||
- [ ] 6d: in memory, `pending`, `hidden` and `creating` by lid; `is_local_uid` checks
|
- [x] 6d: a node written under its stand-in after it landed updates its landed row
|
||||||
become `remote_uid().is_none()`; a write or rename after landing queues under the
|
instead of bringing the placeholder row back, so a write that finds its create
|
||||||
stand-in, so `landed_uid`, `follow_landed_create`, `landed_row_uid` and the
|
landed takes the uid from the row and no longer polls the tree for up to 2 s
|
||||||
`rewrite_op_target` fallback go.
|
(`landed_uid` is gone). In memory, `pending`, the uploads and the tree stay keyed by
|
||||||
|
the uid Drive knows: ops are matched by lid in the database (6b), `hidden` and
|
||||||
|
`creating` only ever hold remote uids, and `landed_row_uid` is the one place at the
|
||||||
|
mount's edge where a stand-in a handle still holds becomes the real uid.
|
||||||
- [ ] An executor that runs ops in dependency order, many at once.
|
- [ ] An executor that runs ops in dependency order, many at once.
|
||||||
3. **Speed (§7)**: run independent ops in parallel, batch trashes and moves; measure 1,000 files
|
3. **Speed (§7)**: run independent ops in parallel, batch trashes and moves; measure 1,000 files
|
||||||
and 100 folders on Wi-Fi against LAN (target: within 10 %).
|
and 100 folders on Wi-Fi against LAN (target: within 10 %).
|
||||||
|
|||||||
Reference in new issue
Block a user