mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
fix(fuse): list again when a listing changed while it was published (B159)
A lookup in the root publishes the "Shared with me" entry only if the root listing did not change while its plan was read, and answered EAGAIN when it had. Right after a restart the root is listed again as the first lookups come in, so a write could fail with "Resource temporarily unavailable". ensure_children now takes the listing again, up to four times.
This commit is contained in:
1 parent
c2f93449ad
commit
60445d3b26
4 files changed
+70
-3
No files matched your search
@@ -2437,7 +2437,24 @@ impl Core {
|
||||
/// Enumerate `ino`'s children from the remote and cache them. No-op if the
|
||||
/// directory has already been listed. Network I/O happens without the lock
|
||||
/// held so concurrent metadata reads aren't blocked behind a fetch.
|
||||
///
|
||||
/// A listing that changed while it was being published answers EAGAIN: the
|
||||
/// root listed again after a restart, an event, a newer refresh of a shared
|
||||
/// folder. The listing is still owed, so it is taken again rather than
|
||||
/// handed to the lookup or open that asked (B159).
|
||||
fn ensure_children(&self, ino: u64) -> Result<(), Errno> {
|
||||
for _ in 1..LISTING_ATTEMPTS {
|
||||
match self.list_children(ino) {
|
||||
Err(e) if e.code() == Errno::EAGAIN.code() => {
|
||||
debug!(ino, "listing changed while it was published; listing again");
|
||||
}
|
||||
done => return done,
|
||||
}
|
||||
}
|
||||
self.list_children(ino)
|
||||
}
|
||||
|
||||
fn list_children(&self, ino: u64) -> Result<(), Errno> {
|
||||
let (folder_uid, cached) = {
|
||||
let st = self.state();
|
||||
match st.entries.get(&ino) {
|
||||
@@ -5777,6 +5794,10 @@ fn api_code(e: &(dyn std::error::Error + 'static)) -> Option<ResponseCode> {
|
||||
}
|
||||
}
|
||||
|
||||
/// How often [`Core::ensure_children`] takes a listing that changed while it
|
||||
/// was being published.
|
||||
const LISTING_ATTEMPTS: usize = 4;
|
||||
|
||||
/// How often a rename or move that Drive calls out of date is retried, and how
|
||||
/// long apart.
|
||||
const OUT_OF_DATE_RETRIES: u32 = 20;
|
||||
@@ -8776,11 +8797,28 @@ mod tests {
|
||||
assert!(changed.contains("self.note_self_change(uid)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_listing_changed_while_published_is_taken_again() {
|
||||
// The root listed again after a restart while a lookup was publishing
|
||||
// the "Shared with me" entry, and the lookup answered EAGAIN (B159).
|
||||
let ensure = function_source(include_str!("lib.rs"), "fn ensure_children(");
|
||||
assert_before(
|
||||
ensure,
|
||||
"for _ in 1..LISTING_ATTEMPTS",
|
||||
"Errno::EAGAIN.code()",
|
||||
);
|
||||
assert_eq!(ensure.matches("self.list_children(ino)").count(), 2);
|
||||
assert!(
|
||||
!ensure.contains("?"),
|
||||
"an attempt's error must not skip the retry"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_folder_lists_from_the_db_while_the_link_is_down() {
|
||||
// A listing dropped as stale mid-run, then a drop: every create in that
|
||||
// folder looks the name up first, and answered EIO for want of it.
|
||||
let ensure = function_source(include_str!("lib.rs"), "fn ensure_children(");
|
||||
let ensure = function_source(include_str!("lib.rs"), "fn list_children(");
|
||||
assert_before(
|
||||
ensure,
|
||||
"if !self.is_online()",
|
||||
|
||||
@@ -12,6 +12,31 @@ Conventions:
|
||||
|
||||
---
|
||||
|
||||
## B159 — A write right after a restart fails with "Resource temporarily unavailable"
|
||||
|
||||
**Status:** Fixed (unverified).
|
||||
**Found:** 2026-10-04, by the simulation's flaky-link profile (`one_client_on_a_flaky_link`,
|
||||
seed 4, one of ten runs on c2f9344). Half a second after the client restarted, a write into its
|
||||
folder answered `EAGAIN`, and nothing was logged. The folder was still a queued create.
|
||||
|
||||
**Where:** `Core::ensure_children` in `crates/pdfs-fuse/src/lib.rs`.
|
||||
|
||||
**Cause.** A lookup in the root also places the "Shared with me" entry. It reads that entry's
|
||||
plan from the database against a snapshot of the root listing, then publishes it only if the
|
||||
listing has not changed meanwhile, and answers `EAGAIN` if it has. Right after a restart the
|
||||
root is listed again while the first lookups come in, so a lookup could lose that race. Listing
|
||||
a shared folder answers `EAGAIN` the same way when a newer refresh overtook it. No caller
|
||||
retries, so the error reached the application.
|
||||
|
||||
**Fix.** `ensure_children` takes a listing again, up to four times, when an attempt answers
|
||||
`EAGAIN`, and logs each retry at debug level.
|
||||
|
||||
**Test:** unit test `a_listing_changed_while_published_is_taken_again`. A stress test with
|
||||
lookups and creates in the root at once did not hit the window (about 1,900 plans, none stale),
|
||||
so the race itself has no regression test.
|
||||
|
||||
---
|
||||
|
||||
## B158 — A file renamed twice in a row ends up under the first name, again
|
||||
|
||||
**Status:** Fixed (unverified).
|
||||
|
||||
@@ -46,6 +46,8 @@ by its local id, in the queue, the stored tree and pins alike.
|
||||
lost Drive's answer to reading back the file's last upload** (B157).
|
||||
- **A file renamed twice in a row, while the first name was still taken, keeps the second name**
|
||||
(B158).
|
||||
- **Opening a file right after the daemon restarts no longer fails with "Resource temporarily
|
||||
unavailable"** (B159).
|
||||
- **A file renamed just as its upload finished keeps its new name.** The mount no longer showed
|
||||
the old name, and a second rename no longer failed with "No such file or directory" (B152).
|
||||
- **A change that waits for a name to be freed no longer waits seconds after it is.** A rename or
|
||||
|
||||
@@ -9,7 +9,7 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
||||
- **Phase 2**: local ids, inode = local id, one tree for every mount (2.10.0).
|
||||
- **Phase 3, first part**: mkdir, create, rename, unlink and rmdir on the mount are recorded
|
||||
locally and sent from the queue; `"local_first": false` brings back the old path (5e0f30c).
|
||||
- Fixes from the simulation and account runs since then: B127 and B141 to B158.
|
||||
- Fixes from the simulation and account runs since then: B127 and B141 to B159.
|
||||
- **Sync issues, first cut** (§6.2): refusals sorted into issues, shown at once in the app,
|
||||
tray and CLI; Export button and `pdfs sync export`; `pdfs sync issues`.
|
||||
|
||||
@@ -82,7 +82,9 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
||||
again after its create lost its answer (B156, fixed). With sixteen drain workers
|
||||
(16fca10), seed 4 failed every replay on a write after an upload that was not read
|
||||
back (B157, fixed). Seed 3 then failed four of ten runs on two renames of one file
|
||||
landing out of order (B158, fixed).
|
||||
landing out of order (B158, fixed). Flaky-link seed 4 failed one of ten runs on a
|
||||
lookup that answered `EAGAIN` while the root was listed again after a restart (B159,
|
||||
fixed).
|
||||
- [ ] Known-bug list in `sim/run.rs` is empty: B125 and B130 open; B127, B129 and B132 fixed
|
||||
but unconfirmed.
|
||||
- [ ] A clean account run. The one on a6f36d5 (2026-10-04, LAN: 427 passed, 12 failed)
|
||||
|
||||
Reference in new issue
Block a user