mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
fix(fuse): a kernel notice sent from the runtime hands its worker's tasks on (B130)
This commit is contained in:
1 parent
e743cd60cf
commit
a68dae9422
4 files changed
+79
-17
No files matched your search
+64
-10
@@ -1364,24 +1364,42 @@ impl KernelChannel {
|
|||||||
|
|
||||||
pub(crate) fn inval_inode(&self, ino: u64) -> std::io::Result<()> {
|
pub(crate) fn inval_inode(&self, ino: u64) -> std::io::Result<()> {
|
||||||
let ino = filesystem::kernel_ino(self.root, ino);
|
let ino = filesystem::kernel_ino(self.root, ino);
|
||||||
self.notifier.inval_inode(ino, 0, 0)
|
off_the_runtime(|| self.notifier.inval_inode(ino, 0, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop the cached attributes of `ino` but not its page cache.
|
/// Drop the cached attributes of `ino` but not its page cache.
|
||||||
fn inval_attrs(&self, ino: u64) -> std::io::Result<()> {
|
fn inval_attrs(&self, ino: u64) -> std::io::Result<()> {
|
||||||
let ino = filesystem::kernel_ino(self.root, ino);
|
let ino = filesystem::kernel_ino(self.root, ino);
|
||||||
self.notifier.inval_inode(ino, -1, 0)
|
off_the_runtime(|| self.notifier.inval_inode(ino, -1, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn inval_entry(&self, parent: u64, name: &OsStr) -> std::io::Result<()> {
|
pub(crate) fn inval_entry(&self, parent: u64, name: &OsStr) -> std::io::Result<()> {
|
||||||
let parent = filesystem::kernel_ino(self.root, parent);
|
let parent = filesystem::kernel_ino(self.root, parent);
|
||||||
self.notifier.inval_entry(parent, name)
|
off_the_runtime(|| self.notifier.inval_entry(parent, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete(&self, parent: u64, child: u64, name: &OsStr) -> std::io::Result<()> {
|
fn delete(&self, parent: u64, child: u64, name: &OsStr) -> std::io::Result<()> {
|
||||||
let parent = filesystem::kernel_ino(self.root, parent);
|
let parent = filesystem::kernel_ino(self.root, parent);
|
||||||
let child = filesystem::kernel_ino(self.root, child);
|
let child = filesystem::kernel_ino(self.root, child);
|
||||||
self.notifier.delete(parent, child, name)
|
off_the_runtime(|| self.notifier.delete(parent, child, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a notice to the kernel without holding up the runtime.
|
||||||
|
///
|
||||||
|
/// The kernel answers an invalidation only once it has the pages' locks, and
|
||||||
|
/// a read holds those until the daemon answers it. Sent from a runtime worker,
|
||||||
|
/// the notice blocked that worker in the kernel; when it was the one driving
|
||||||
|
/// the timers and I/O, nothing drove them any more, so the read's fetch never
|
||||||
|
/// finished and the daemon hung (`docs/BUGS.md` B130). On a worker the notice
|
||||||
|
/// hands the worker's tasks to another thread first; anywhere else it is sent
|
||||||
|
/// as it is.
|
||||||
|
fn off_the_runtime<T>(notice: impl FnOnce() -> T) -> T {
|
||||||
|
match tokio::runtime::Handle::try_current() {
|
||||||
|
Ok(handle) if handle.runtime_flavor() == tokio::runtime::RuntimeFlavor::MultiThread => {
|
||||||
|
tokio::task::block_in_place(notice)
|
||||||
|
}
|
||||||
|
_ => notice(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7319,12 +7337,12 @@ mod tests {
|
|||||||
SELF_CHANGE_TTL_MS, ShareId, SharedWithMeItem, StateRegistry, VirtualRootPlan,
|
SELF_CHANGE_TTL_MS, ShareId, SharedWithMeItem, StateRegistry, VirtualRootPlan,
|
||||||
accepted_share_provenance, conflict_name, copy_pending_for_truncate, expand_restore,
|
accepted_share_provenance, conflict_name, copy_pending_for_truncate, expand_restore,
|
||||||
first_seen_in_trash, fuse_name, is_stale_mount, node_visible, note_self_change,
|
first_seen_in_trash, fuse_name, is_stale_mount, node_visible, note_self_change,
|
||||||
orphan_past_retention, parse_node_uid, prepare_shared_roots, preserve_on_access_denied,
|
off_the_runtime, orphan_past_retention, parse_node_uid, prepare_shared_roots,
|
||||||
publish_virtual_root_in_listing, reconcile_virtual_root_in_listing,
|
preserve_on_access_denied, publish_virtual_root_in_listing,
|
||||||
release_can_discard_unlinked, release_must_retain_queued_trash, release_unlinked_entry,
|
reconcile_virtual_root_in_listing, release_can_discard_unlinked,
|
||||||
rename_needs_queue, require_node_parent_access, require_rename_access,
|
release_must_retain_queued_trash, release_unlinked_entry, rename_needs_queue,
|
||||||
resolve_anywhere_with, shared_with_me_uid, take_self_change, uid_write_authority,
|
require_node_parent_access, require_rename_access, resolve_anywhere_with,
|
||||||
virtual_node,
|
shared_with_me_uid, take_self_change, uid_write_authority, virtual_node,
|
||||||
};
|
};
|
||||||
use super::{Db, WriteAuthority};
|
use super::{Db, WriteAuthority};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -9000,6 +9018,42 @@ mod tests {
|
|||||||
assert!(remote.contains("async { tokio::time::timeout(budget, fetch).await }"));
|
assert!(remote.contains("async { tokio::time::timeout(budget, fetch).await }"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_kernel_notice_sent_from_the_runtime_leaves_its_timers_running() {
|
||||||
|
// A notice waits in the kernel for a read whose fetch waits on a
|
||||||
|
// timer of the same runtime (B130). Here the runtime's one worker sends
|
||||||
|
// it, and the timer has to fire for the notice to return.
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(1)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let (fetched, fetch) = std::sync::mpsc::channel();
|
||||||
|
rt.spawn(async move {
|
||||||
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||||
|
let _ = fetched.send(());
|
||||||
|
});
|
||||||
|
let notice =
|
||||||
|
rt.spawn(async move { off_the_runtime(|| fetch.recv_timeout(Duration::from_secs(5))) });
|
||||||
|
assert!(rt.block_on(notice).unwrap().is_ok());
|
||||||
|
|
||||||
|
// Every notice to a session goes through it.
|
||||||
|
let lib = include_str!("lib.rs");
|
||||||
|
for method in [
|
||||||
|
"fn inval_inode(&self",
|
||||||
|
"fn inval_attrs(&self",
|
||||||
|
"fn inval_entry(&self",
|
||||||
|
"fn delete(&self, parent: u64",
|
||||||
|
] {
|
||||||
|
let source = function_source(lib, method);
|
||||||
|
assert_eq!(source.matches("self.notifier.").count(), 1, "{method}");
|
||||||
|
assert!(
|
||||||
|
source.contains("off_the_runtime(|| self.notifier."),
|
||||||
|
"{method}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_deadline_built_under_block_on_needs_no_ambient_runtime() {
|
fn a_deadline_built_under_block_on_needs_no_ambient_runtime() {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
|||||||
+11
-4
@@ -1218,7 +1218,7 @@ timing, and then on the refused write.
|
|||||||
|
|
||||||
## B130 — A read of a file that a remote change invalidates can hang the daemon
|
## B130 — A read of a file that a remote change invalidates can hang the daemon
|
||||||
|
|
||||||
**Status:** Open.
|
**Status:** Fixed.
|
||||||
**Found:** 2026-10-03, by the simulation runs (`sim::run`, profile one-client-flaky, seed 1).
|
**Found:** 2026-10-03, by the simulation runs (`sim::run`, profile one-client-flaky, seed 1).
|
||||||
After the last step, a read of `c0/z/f.txt` never returned. The watchdog's stacks showed the
|
After the last step, a read of `c0/z/f.txt` never returned. The watchdog's stacks showed the
|
||||||
read's worker in `read_range_remote`, waiting in `block_on` for its fetch, and a runtime worker
|
read's worker in `read_range_remote`, waiting in `block_on` for its fetch, and a runtime worker
|
||||||
@@ -1239,9 +1239,16 @@ sleep on a condvar. The read's fetch waits on a timer (here the fake's latency,
|
|||||||
HTTP response), so it never finishes and never lets go of the page lock. The daemon stays stuck
|
HTTP response), so it never finishes and never lets go of the page lock. The daemon stays stuck
|
||||||
until some other work wakes a worker, and on an idle machine that may never happen.
|
until some other work wakes a worker, and on an idle machine that may never happen.
|
||||||
|
|
||||||
**Test:** the simulation runs recognise it in the watchdog's stacks (a `tokio-rt-worker` in
|
**Fix.** Every notice to the kernel goes through `off_the_runtime`, which on a runtime worker
|
||||||
state `D` inside `NotifyBatch::flush`), count it and end the seed there. With
|
sends it from inside `block_in_place`: the worker's tasks, and the driver with them, go to
|
||||||
`PDFS_SIM_KNOWN=fail`, seed 1 of one-client-flaky fails on it.
|
another thread while it waits. Anywhere else the notice is sent as before.
|
||||||
|
|
||||||
|
**Test:** `a_kernel_notice_sent_from_the_runtime_leaves_its_timers_running` in
|
||||||
|
`crates/pdfs-fuse/src/lib.rs`: the one worker of a runtime sends a notice that returns only once
|
||||||
|
a timer of that runtime has fired. The simulation runs still recognise it in the watchdog's
|
||||||
|
stacks (a `tokio-rt-worker` in state `D` inside `NotifyBatch::flush`); with
|
||||||
|
`PDFS_SIM_KNOWN=fail`, seed 1 of one-client-flaky fails on it, though it no longer reaches it
|
||||||
|
since phase 3.
|
||||||
|
|
||||||
## B129 — A file deleted or replaced while its create is on the wire stays on Drive
|
## B129 — A file deleted or replaced while its create is on the wire stays on Drive
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ by its local id, in the queue, the stored tree and pins alike.
|
|||||||
worker missed the stop (B180).
|
worker missed the stop (B180).
|
||||||
- **A folder made while offline or with sync paused no longer fails with "Input/output
|
- **A folder made while offline or with sync paused no longer fails with "Input/output
|
||||||
error"** once it is refreshed or the connection comes back (B125).
|
error"** once it is refreshed or the connection comes back (B125).
|
||||||
|
- **The daemon no longer hangs when a file being read changes on Drive** (B130).
|
||||||
- **A file renamed just as its upload finished keeps its new name.** The mount no longer showed
|
- **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,
|
the old name, and a second rename no longer failed with "No such file or directory" (B152,
|
||||||
B172).
|
B172).
|
||||||
|
|||||||
@@ -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 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
|
- **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).
|
locally and sent from the queue; `"local_first": false` brings back the old path (5e0f30c).
|
||||||
- Fixes from the simulation and account runs since then: B125, B127 and B141 to B180.
|
- Fixes from the simulation and account runs since then: B125, B127, B130 and B141 to B180.
|
||||||
- **Sync issues, first cut** (§6.2): refusals sorted into issues, shown at once in the app,
|
- **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`.
|
tray and CLI; Export button and `pdfs sync export`; `pdfs sync issues`.
|
||||||
|
|
||||||
@@ -89,8 +89,8 @@ Tracks `docs/MILESTONE-3.0.0.md`. Update it with every commit that moves a phase
|
|||||||
create was on the wire, which the mount showed under its old name (B161, fixed).
|
create was on the wire, which the mount showed under its old name (B161, fixed).
|
||||||
Seed 4 failed on a folder removed and made again, whose create adopted the removed
|
Seed 4 failed on a folder removed and made again, whose create adopted the removed
|
||||||
folder (B162, fixed). CI on 903452a passed every profile.
|
folder (B162, fixed). CI on 903452a passed every profile.
|
||||||
- [ ] Known-bug list in `sim/run.rs` is empty: B130 open; B125, B127, B129 and B132 fixed but
|
- [ ] Known-bug list in `sim/run.rs` is empty: none open; B125, B127, B129, B130 and B132
|
||||||
unconfirmed.
|
fixed but not yet confirmed by the runs.
|
||||||
- [ ] A clean account run. The one on a6f36d5 (2026-10-04, LAN: 427 passed, 12 failed)
|
- [ ] A clean account run. The one on a6f36d5 (2026-10-04, LAN: 427 passed, 12 failed)
|
||||||
failed B113 and B121 on every on-demand mount, two mirror-to-mirror move cases, and
|
failed B113 and B121 on every on-demand mount, two mirror-to-mirror move cases, and
|
||||||
the journal check. B121 was a bug (B149, fixed); B113's case still expected creates
|
the journal check. B121 was a bug (B149, fixed); B113's case still expected creates
|
||||||
|
|||||||
Reference in new issue
Block a user