fix(fuse,scripts): log why a create finds no parent and fit the metadata floor to FUSE

A create that answered ENOENT left no trace in the log. It now warns
when the parent folder is no longer known, and when the parent left the
tree while its children were being listed, naming the parent inode and
the new name.

The acceptance script's metadata throughput floor was 20 operations per
second, which a FUSE mount cannot reach: each create and unlink waits for
Drive before it returns, about 350 ms apiece. On a FUSE mount the floor
is now 3 operations per second; other paths keep 20. The mount type is
read from /proc/self/mountinfo.
This commit is contained in:
nvrl committed 2026-09-29 21:22:54 +02:00
1 parent 54e9921efb
commit cb38f79aa3
3 files changed
+33 -2

No files matched your search

+10
View File
@@ -1464,6 +1464,12 @@ impl ProtonFs {
return;
}
if let Err(e) = self.core.ensure_children(parent) {
// The parent was looked up a moment ago, so ENOENT here means it
// left the tree in between. Log it: the caller only sees "no such
// file" on a create.
if e == Errno::ENOENT {
warn!(parent, name, "create: the parent folder is no longer known");
}
reply.error(e);
return;
}
@@ -1472,6 +1478,10 @@ impl ProtonFs {
match st.entries.get(&parent) {
Some(e) => e.uid.clone(),
None => {
warn!(
parent,
name, "create: the parent folder left the tree during its listing"
);
reply.error(Errno::ENOENT);
return;
}
+2 -1
View File
@@ -97,7 +97,8 @@ Besides the syscall contract, the suite covers:
- **Throughput.** 64 MiB written and read sequentially, 1 MiB in 4 KiB writes,
and metadata operations on 100 files. The rates are printed. The case fails
below `PDFS_ACCEPTANCE_MIN_MIBPS` (default 10) or `PDFS_ACCEPTANCE_MIN_OPS`
(default 20 operations per second).
(default 20 operations per second, or 3 on a FUSE mount, where each create
and unlink waits for Drive before it returns).
### The mount is diffed against an ordinary filesystem
+21 -1
View File
@@ -1357,9 +1357,15 @@ def test_throughput(ctx: Context) -> None:
reads of just-written data come from the cache, so neither should depend
on the network. PDFS_ACCEPTANCE_MIN_MIBPS and PDFS_ACCEPTANCE_MIN_OPS move
the floors; 0 turns one off.
Metadata is the exception. On a Drive mount each create and each unlink
waits for Drive before it returns (about 350 ms for a create), so 20
operations a second is out of reach there by design. The default floor for
a FUSE target is 3, which still catches a second round trip per operation.
"""
min_rate = float(os.environ.get("PDFS_ACCEPTANCE_MIN_MIBPS", "10"))
min_ops = float(os.environ.get("PDFS_ACCEPTANCE_MIN_OPS", "20"))
default_ops = "3" if is_fuse(ctx.root) else "20"
min_ops = float(os.environ.get("PDFS_ACCEPTANCE_MIN_OPS", default_ops))
root = ctx.root / "throughput"
root.mkdir()
chunk = pattern(MIB, "throughput")
@@ -3308,6 +3314,20 @@ def is_mountpoint(path: Path) -> bool:
return os.path.ismount(path)
def is_fuse(path: Path) -> bool:
"""True when `path` lives on a FUSE mount, read from /proc/self/mountinfo."""
path = path.resolve()
best, fstype = -1, ""
with open("/proc/self/mountinfo", encoding="utf-8") as mounts:
for line in mounts:
fields = line.split()
# Octal escapes (\040 for a space) are how mountinfo writes odd names.
point = Path(fields[4].encode().decode("unicode_escape"))
if (path == point or point in path.parents) and len(point.parts) > best:
best, fstype = len(point.parts), fields[fields.index("-") + 1]
return fstype.startswith("fuse")
def run_managed_matrix(
paths: list[Path], reference: Run, args, janitor: Janitor | None = None
) -> None: