Skip to content

feat(dav): serialize concurrent MOVE/COPY - #62727

Open
TobyTheHutt wants to merge 1 commit into
nextcloud:masterfrom
TobyTheHutt:feat/62648/serialise-webdav-move-copy
Open

feat(dav): serialize concurrent MOVE/COPY#62727
TobyTheHutt wants to merge 1 commit into
nextcloud:masterfrom
TobyTheHutt:feat/62648/serialise-webdav-move-copy

Conversation

@TobyTheHutt

@TobyTheHutt TobyTheHutt commented Jul 31, 2026

Copy link
Copy Markdown

Summary

Serialize concurrent WebDAV MOVE and COPY on the same source or destination path via a new SerializeMoveCopyPlugin registered at the DAV entry. When enabled, a conflicting concurrent operation MUST receive HTTP 423 Locked. Concurrent COPY from one source to different destinations remains allowed.

Lock scheme

Method Source lock Destination lock
MOVE LOCK_EXCLUSIVE LOCK_EXCLUSIVE
COPY LOCK_SHARED LOCK_EXCLUSIVE

Locks share the namespace webdav-serialize:<davPath>. Path-sorted acquisition prevents deadlock between two concurrent operations with swapped source and destination. Source == destination short-circuits with no lock (defensive).

Config

Set dav.serialize_move_copy to true in config/config.sample.php. Defaults to false.

Behaviour matrix (op B arrives while A in flight)

A B Same source Same destination B outcome
MOVE MOVE yes either 423
MOVE COPY yes either 423
COPY MOVE yes either 423
COPY COPY yes different 201
COPY COPY yes same 423
MOVE MOVE different same 423
COPY COPY different same 423
MOVE COPY different same 423
COPY MOVE different same 423
any any different different 201

Cross-user contention on the same shared file is serialized one layer down. The md5 lock key at Common.php:696 collapses both users onto one key (owner-storage id plus owner-internal path). ObjectTree::copy wraps the storage-layer \OCP\Lock\LockedException to HTTP 423 for COPY. Node::setName does not wrap for MOVE and surfaces HTTP 500..

Known limitations

  • SHARED starvation of EXCLUSIVE. A continuous stream of concurrent COPYs on a source blocks a MOVE on that source indefinitely. ILockingProvider provides no fair-lock primitive.
  • Backend TTL vs long request. filelocking.ttl default 3600 s exceeds typical max_execution_time. The lock outlives the request only on runaway workloads. Same posture as inner-lock behaviour.

TODO

  • Implementation
  • Unit tests
  • E2E matrix on the .devcontainer stack

Checklist

  • Code is properly formatted
  • Sign-off message is added to all commits
  • Tests (unit) are included at apps/dav/tests/unit/Connector/Sabre/SerializeMoveCopyPluginTest.php. Unit suite: 11 tests, 48 assertions, all passing on PHP 8.4.23 + PHPUnit 11.5.50. E2E matrix (10 rows, .devcontainer stack, 5 MB source, 100 ms stagger): all rows PASS. Contended rows produce one 20x and one 423. Allowed rows (fan-out COPY) produce two 20x.
  • Screenshots before/after for front-end changes
  • Documentation (manuals or wiki) has been updated or is not required
  • Backports requested where applicable (ex: critical bugfixes)
  • Labels added where applicable (ex: bug/enhancement, 3. to review, feature component)
  • Milestone added for target branch/version (ex: 32.x for stable32)

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@TobyTheHutt
TobyTheHutt requested a review from a team as a code owner July 31, 2026 11:12
@TobyTheHutt
TobyTheHutt requested review from Altahrim, come-nc, icewind1991 and leftybournes and removed request for a team July 31, 2026 11:13
@joshtrichards

joshtrichards commented Jul 31, 2026

Copy link
Copy Markdown
Member

Cross-user contention on shared files falls through to inner storage-scoped locks. SharedStorage::getId() is user-scoped. A file shared from Alice to Bob resolves to distinct storage IDs per user. DAV-path keying cannot collapse the two users onto a single lock key. File-id keying is unavailable at beforeMove/beforeCopy because the destination has no id pre-creation. Deferred.

I think we can do this even more cleanly by implementing actual operation-scoped locking below DAV so UI/jobs/other Node/View callers can participate too. It'll provide broader coverage outside of just DAV and not have similar limitations.

Technically we already have the capability today using our existing locking primitives and w/o introducing a new locking layer (and use it in the encryption app in a small way), but I want to wrap it anyhow to make a cleaner API (internal for now) as a formal operational locking service.

transactional file locks  → filesystem access safety (exists today)
files_lock                → persistent collaboration/edit policy (exists today)
operation advisory locks  → short-lived workflow serialization (can be done today using underlying existing locking primitives)

I actually got about 80% of the way through a proof-of-concept when I saw your Issue pop up the other day (implementing the internal API + adjusting View::copy() and View::rename() to use it).

@TobyTheHutt

Copy link
Copy Markdown
Author

You're right. Common::acquireLock computes the md5 from the owner storage's id and owner-internal path via SharedStorage::acquireLock delegating to the owner. So cross-user contention on the same underlying shared file collapses onto one storage-layer key today already . I updated the PR description.

Thanks for raising this!

The DAV plugin in this PR rejects at request entry via beforeMove/beforeCopy, so a losing WebDAV client sees a clean 423 without the request traversing the full Sabre --> View --> Storage stack. A below-DAV service would surface contention only after Sabre dispatch.

Assisted-by: ClaudeCode:claude-opus-4-7
Signed-off-by: Tobias Harnickell <tobias.harnickell@bedag.ch>
@DerDreschner
DerDreschner force-pushed the feat/62648/serialise-webdav-move-copy branch from fb6d331 to 78e3736 Compare July 31, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serialise concurrent WebDAV MOVE and COPY against the same source

2 participants