Commit Graph
7 Commits
Author SHA1 Message Date
Chris LuandClaude Opus 5.5 f2e446cf5b mount: retry a read view whose merged manifests did not resolve
A failed SetChunks leaves the chunk group answering every read with the
resolution error, and nothing re-ran it while the entry stayed the same.
The handle now marks such a view (on rebase and on SetEntry) and the
next read or lseek resolves the entry's chunks again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:19:01 -08:00
Chris LuandClaude Opus 5.5 1f2ed6923f mount: keep a peer's truncate when the handle left the size alone
The merge took the larger of the handle's and the stored size, so a peer
truncate came back as a zero-filled tail when the handle only overwrote
bytes inside it. The stored size now stands unless the handle changed
its size or wrote past it, and rebasing keeps only the growth writes made
after the flush was taken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:19:01 -08:00
Chris LuandClaude Opus 5.5 f7be2a75f5 mount: clip peer chunks at the lowest truncate, not the final size
A handle that truncated and then wrote past the old end ends up larger
than its base, so the merge did not see the truncate and kept the peer's
chunks inside the range the truncate cleared. SetAttr now records the
lowest size truncated to over the current base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:19:01 -08:00
Chris LuandClaude Opus 5.5 8499d787cd mount: refuse to merge a whole-file rewrite over a peer's chunk change
maybeMergeChunks re-uploads the handle's whole view as unstamped chunks.
Merged after a peer's change, they tie with a peer's own rewrite or bring
back a range the peer truncated, so that flush fails as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:12:38 -08:00
Chris LuandClaude Opus 5.5 3fc45d9f83 mount: refuse a merge that puts inline content next to chunks
A read inside the inline bytes returns them without looking at chunks,
so one side's inline content merged with the other side's chunks hid
data after a successful flush.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:12:13 -08:00
Chris LuandClaude Opus 5.5 bb7e4692c0 mount: merge a refused content flush onto the stored entry
A content flush conditioned on the handle's base failed with EIO whenever a
peer had changed the file since, so two mounts appending to one file could
not both close it cleanly. On a refused condition, reread the entry and
replay this handle's changes since its base onto it: the chunks it added or
dropped, and the attributes, extended keys and inline content it changed.
A local truncation keeps its size and clips the peer's chunks to it;
otherwise the larger size wins. The merged entry is published against the
stored one, up to three times, and installed in the handle so its next
flush keeps the peer's chunks. A replaced or deleted file is still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:48:21 -08:00
d7ae08ecad fix(mount): refuse stale whole-entry content flushes (#12594)
* fix(mount): condition content flushes on the observed entry

* mount: stop retrying a flush the filer refused on its condition

The filer reports a failed CreateEntry condition as FilerError_PRECONDITION_FAILED
in the response, which surfaces as filer_pb.ErrPreconditionFailed rather than a
FailedPrecondition status. The flush retry loop treated it as transient and
replayed the refused flush three more times over 7 s before returning EIO.

The test filer now answers like the real one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mount: take the handle base from the entry the filer stored

After an acknowledged flush the handle base was the requested entry, but the
filer stores it with a fresh commit_ts_ns (and fills crtime, inode, atime), so
the next IF_ENTRY_EQUAL flush on the same handle never matched: write, fsync,
write, fsync returned EIO, as did close after a periodic metadata flush and the
first flush after an eager create. Use the stored entry from the
acknowledgement's metadata event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mount: condition the periodic metadata flush like the close flush

The periodic metadata flush of an open, dirty handle sent the same whole entry
through an unconditional CreateEntry, so a file held open past the flush
interval still overwrote a peer's acknowledged append, and the close flush then
matched the overwritten state. Both paths now publish through one helper that
carries the handle's write condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mount: republish a refused flush once when the stored entry is unchanged

A handle's base can differ from the stored entry without any peer write: a
rename while the file is open restamps the entry under its new name, and with
-map.uid a filer id that is also a mapped local id does not map back to itself.
Each made every flush of the handle fail with EIO. On a miss, read the stored
entry and, if it still holds the observed state apart from name, commit stamp,
id encoding and derived size, retry once with that entry as the condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mount: take the filer's entry once a write open holds the DLM lock

The lock orders this writer after the previous holder, but the holder's
flush may not have reached the local cache yet, so the handle's base
named the entry from before the wait and the conditional flush refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mount: share the open-existing path of Create

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mount: open the file a previous DLM holder created during a deferred create

A deferred create never asks the filer, so a create that waited for the
lock went on to publish over a file the previous holder had just made,
and its flush expecting no entry was refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* master: send the lock ring snapshot to mounts

A mount running -dlm is a lock client like the S3 gateway, and needs the
ring on connect to reach a file's owner filer before the next membership
change is broadcast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mount: follow the masters' lock ring under -dlm

The lock client only knew the seed filer, so every lock took the forward
hop and the mount had no way to tell which filer owns a file. Subscribe to
the masters named by the filer, like the S3 gateway, and wait briefly for
the first ring before serving.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mount: read the DLM-ordered entry from the file's owner filer

The filer checks a flush's condition on the owner of the path's write
route key, and another filer may not have applied the previous holder's
flush yet. Look the entry up on that owner once the ring is known.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:15:08 -08:00