Repository navigation
[DEFECT] Content Drive: a folder can be duplicated while another user is deleting it, and the delete rolls back #37895
Description
Problem Statement
Content Drive lets a user duplicate a folder that another user is deleting. The duplicate's copy can land inside the folder being deleted, and then the whole delete fails and rolls back with a raw database error. Neither user is warned.
Nothing coordinates the two operations:
- Bulk folder delete (Content Drive: bulk folder delete, async job endpoint and frontend wiring #37063) refuses a submission that overlaps another delete in progress (
409,FolderBulkDeleteHelper.checkForOverlap), but only looks at its own queue. - Bulk folder duplicate (Content Drive: folder copy, async job endpoint and frontend wiring #37062) only waits for other duplicates landing in the same parent (
pg_advisory_xact_lockkeyed on the parent,FolderDuplicator.java:127). - So
POST /api/v1/assets/folders/_bulkduplicateaccepts a folder that an in-progress delete covers.
The Content Drive UI marks folders being deleted as busy (with-folder-delete-runs.ts, allBusyRows, read from the server's list of active delete runs). The duplicate endpoint does not check, and the busy marking is read when the page loads, so it may not cover a delete another user starts afterwards.
Impact:
- One user's duplicate undoes another user's delete. In our test the delete ran for ~2.5 minutes on 15,000 files and then rolled back completely. A 20,000-file folder took ~8 minutes to delete in an earlier measurement.
- The deleting user gets a raw SQL error with no hint that someone else caused it.
- More generally, any change to content while it is being deleted is unsafe. While a delete is running, nothing else should act on what it covers.
Steps to Reproduce
Local stack, dotcms/dotcms:trunk_d4a354c, site demo.dotcms.com.
- Create
/qa-delete-long/with a subfolderchild/(5 files) and enough content to make the delete take a few minutes (we used 15,000 small files under/qa-delete-long/bulk/). - As user A (admin), start the delete:
POST /api/v1/assets/folders/_bulkdeletewith{"assetPaths":["//demo.dotcms.com/qa-delete-long/"]}→202. - Wait until the delete job is
RUNNING(GET /api/v1/jobs/{jobId}/status). - As user B (another back-end user), duplicate the subfolder:
POST /api/v1/assets/folders/_bulkduplicatewith{"assetPaths":["//demo.dotcms.com/qa-delete-long/child/"]}.
Actual:
| Time | Event |
|---|---|
| 09:24:22 | The delete starts running |
| ~09:24:24 | User B's duplicate is accepted with 202: no refusal, no warning |
| ~09:24:50 | The duplicate runs and creates child_copy inside /qa-delete-long/. Its job ends SUCCESS. |
| 09:27:09 | The delete reaches the end and fails. Its job reports the folder as FAILED / UNCLASSIFIED: com.dotmarketing.exception.DotDataException: ERROR: Cannot delete as this path has children Where: PL/pgSQL function check_child_assets() line 13 at RAISE SQL statement "DELETE from identifier where …" |
End state: /qa-delete-long/ is intact with 15,012 files (the original 15,007 plus the 5 copied into child_copy). The whole delete was rolled back.
Expected: while a delete is running, the duplicate is refused at submission with a clear message that names the operation and who started it, for example:
"This folder is being deleted by Admin User. It can't be duplicated right now."
Acceptance Criteria
-
_bulkduplicaterefuses, at submission, any folder that is the same as, inside, or a parent of a folder an in-progress delete covers. The response names the folder, says a delete is in progress, and says who started it. - A refused duplicate creates no job, so nothing is copied.
- The same check applies when the job is created through
POST /api/v1/jobs/folderBulkDuplicate(see [DEFECT] /api/v1/jobs/folderBulkDuplicate skips the 50-folder limit that Content Drive's bulk duplicate enforces #37883). - Decide which other operations must also be refused on content an in-progress delete covers, and apply the same rule and message to each. At minimum: duplicate, move (Content Drive: folder move, batched async job endpoint and frontend wiring #37165), upload into it (Content Drive: bulk file upload (multi-file selection uploads only the first file) #37166), and editing or publishing contentlets inside it.
- The UI shows the server's message, and greys out actions on folders being deleted, including deletes another user starts after the page has loaded.
- If a delete still fails because content changed underneath it, the result tells the user that in plain words instead of a raw SQL error.
- An integration test runs a delete and a duplicate on overlapping folders at the same time and asserts the duplicate is refused and the delete completes.
dotCMS Version
Latest from main: dotcms/dotcms:trunk_d4a354c (2026-10-02), which includes #37760, #37761 and the bulk delete from #37063.
Severity
High - Major functionality broken
Links
- Found during QA of Content Drive: folder copy, async job endpoint and frontend wiring #37062 (bulk folder duplicate), related to Content Drive: bulk folder delete, async job endpoint and frontend wiring #37063 (bulk folder delete).
- Related: [DEFECT] /api/v1/jobs/folderBulkDuplicate skips the 50-folder limit that Content Drive's bulk duplicate enforces #37883 (duplicate limit bypass through the generic jobs endpoint), [DEFECT] Content Drive: Duplicating a folder copies subfolders and files the user cannot read #37893 (duplicate copies content the user cannot read), Content Drive: folder move, batched async job endpoint and frontend wiring #37165 (folder move), Content Drive: bulk file upload (multi-file selection uploads only the first file) #37166 (bulk upload).
- NA (no Freshdesk ticket)
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNext Sprint
Activity