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.
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.
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.