Commit Graph
5 Commits
Author SHA1 Message Date
Chris Lu d7854ce88e s3: keep object-lock buckets fully on the distributed lock
objectWriteOwner now returns "" for object-lock (WORM) buckets, so versioned
PutObject / copy / delete-marker no longer route them off the lock. Routed writes
serialize on the owner's per-path entry lock while retention-checked deletes use
the distributed lock, and those two locks don't serialize against each other; an
object-lock bucket that routed some writes and not others would split-brain on
the same object. Retention enforcement is gateway-side and not part of the
per-path-locked filer ops, so the whole bucket stays on the distributed lock and
remains internally consistent. The now-redundant gate is dropped from
routedObjectOwner.
2026-05-23 10:08:22 -07:00
Chris Lu 8acf21e4d1 s3: route suspended-versioning PutObject off the DLM
A suspended-versioning PUT writes the "null" version to the main object path, so
it is a single-entry object write just like a non-versioned PUT — only the
IsLatest-flag rewrite over existing versions differs, and that is best-effort
bookkeeping (recoverable on the next list-versions resync).

So route it on the object key like a normal PUT: putSuspendedVersioningObject
now calls putToFiler without an afterCreate hook (letting the route-by-key path
take it and skip the distributed lock), then runs updateIsLatestFlagsFor-
SuspendedVersioning best-effort after the write instead of inside the lock.

routedObjectOwner now excludes only versioning-*enabled* buckets (whose writes go
to .versions) rather than all versioning-configured buckets, so suspended and
unversioned writes — both targeting the main object path — resolve the same
object-key owner and serialize on the same lock. Object-lock buckets still keep
the lock path; suspended DELETE (delete null + create marker) stays multi-step on
the lock.
2026-05-23 09:52:56 -07:00
Chris Lu 2d41d37150 s3: route non-versioned CompleteMultipartUpload off the DLM
A non-versioned multipart completion's only contended mutation is the final
object CreateEntry; assembling parts and the idempotent-replay check are
read-only. So when the destination is non-versioned, route that final write to
the object's owner with the precondition (reusing the route-by-key path) and run
the assembly outside any lock, skipping the distributed lock and gateway
precondition.

mkFileRouted builds the object entry the way mkFile does, applies the existing
modifier, and routes the conditioned CreateEntry to the owner. Versioned and
suspended completions keep the lock path. This is a cold path (one completion per
upload), so the win is consistency with the other write paths rather than
throughput.
2026-05-23 09:46:02 -07:00
Chris Lu 20934cc854 s3: map routed write/delete errors and fall back on directory markers
Address review feedback on the routed fast paths:

- PutObject: map the response's machine-readable FilerError code to the same S3
  error the lock path produces (key-too-long, parent/existing-is-file,
  existing-is-directory, precondition), instead of re-wrapping resp.Error in a
  fresh error that filerErrorToS3Error can no longer match by sentinel. Unknown
  codes and in-band errors fall back to the lock path for exact semantics.

- DeleteObject: on any non-precondition routed error, fall back to the lock
  path instead of returning InternalError. This restores directory-marker
  handling (demote a marker that still has children rather than failing on a
  non-empty folder), which the raw DeleteEntry call lacks.
2026-05-22 23:07:26 -07:00
Chris Lu 21af729cb4 s3: route single-entry object writes to the owner filer, off the DLM
For non-versioned, non-object-lock buckets, PutObject and unversioned
DeleteObject now send their metadata write straight to the object key's owner
filer (resolved from the lock-ring view) with the precondition attached. The
owner serializes the write with its local per-path lock and evaluates the
condition atomically, so these paths no longer acquire a distributed lock.

The fast path is opt-in per request and falls back to withObjectWriteLock for
anything it does not fully cover: versioned or object-lock buckets, conditions
that do not reduce to a single primitive (ETag lists, weak ETags, time-based,
combined headers), an unresolved owner, or an unreachable owner filer. So
behavior is unchanged outside the safe subset.

Multi-step finalizations (copy, CompleteMultipartUpload) and versioned writes
keep the distributed lock: they mutate several entries under one held lock,
which a single conditional create or delete does not cover.

DeleteEntry gains the same optional WriteCondition and per-path lock as
CreateEntry so a routed conditional delete is atomic on the owner.
2026-05-22 22:48:07 -07:00