[Bug]: Concurrent preview generation for the same spec deletes the winner's file (root cause behind recurring "preview DB record exists but file is missing")
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 36.9k
- Forks
- 5.2k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 713
Description
⚠️ This issue respects the following points: ⚠️
- This is not a troubleshooting question, general support matter, or webserver/proxy problem, but likely a bug (if unsure, ask the Community Help Forum).
- This issue is not already reported on Github OR solved at the Community Help Forum (I've searched!).
- I'm using a maintained major version of Nextcloud Server and tested against the latest patch level. (Supported major versions and current patch levels).
- I agree to follow Nextcloud's Code of Conduct.
- I've tried my best to provide clear reproduction steps that someone unfamiliar with this bug could use to reproduce it.
Bug description
Describe the bug
When two requests concurrently generate a preview for the same file, same
size specification (width/height/crop/max), both write to the same
deterministic on-disk path (derived only from fileId + dimensions, not from
the preview's own DB id). Both then attempt PreviewMapper::insert(). One
succeeds; the other receives REASON_UNIQUE_CONSTRAINT_VIOLATION.
In Generator::savePreview(), the losing request's catch block calls:
$this->storageFactory->deletePreview($previewEntry);
Because the on-disk path is shared/deterministic, this deletes the file that
the winning request just wrote — not a file belonging to the loser. The
result: a valid oc_previews DB row survives (the winner's), but the file
on disk is gone. Every subsequent read fails with NotFoundException /
"Unable to open preview stream", and (prior to #63486-style self-healing)
this state is permanent until manually cleaned up.
This is almost certainly the underlying mechanism behind the pattern
reported in #58787, #63513, and related issues: "DB thinks a preview
exists, disk doesn't."
Why this matters for the #63486 fix
The self-healing approach in #63486 (detect missing file on read, drop the
stale row, regenerate) mitigates the symptom effectively, but does not
address this root cause. Since the regeneration triggered by the self-heal
can itself race with another concurrent request for the same spec (e.g.
multiple participants opening a Talk conversation with a freshly-uploaded
attachment at the same time), the same delete-the-winner's-file sequence can
recur immediately after a self-heal, producing a new stale record right
away. We reproduced this directly: after patching in the #63486-style
existence check, a still-missing icon-size preview needed two regeneration
cycles to converge, and disappeared for one participant while another
(whose request happened not to race) saw it fine.
Steps to reproduce
To Reproduce
Hard to reproduce deterministically (it's a race), but reliably triggered
in practice by: multiple Talk participants opening a conversation with a
recently-uploaded image attachment at roughly the same time, causing
concurrent preview-size requests for the same file/spec.
Expected behavior
Expected behavior
The losing request in the UNIQUE_CONSTRAINT_VIOLATION branch should not
delete the on-disk file, since the path is shared with the winner and the
image content is identical (same source file, same spec). It should simply
fetch and return the winner's already-persisted Preview record via
getPreviewForSpecification(), which the code already does immediately
after the (in our view, unnecessary and harmful) delete call.
Nextcloud Server version
34
Operating system
RHEL/CentOS
PHP engine version
None
Web server
Apache (supported)
Database engine version
MariaDB
Is this bug present after an update or on a fresh install?
None
Are you using the Nextcloud Server Encryption module?
Encryption is Disabled
What user-backends are you using?
- Default user-backend (database)
- LDAP/ Active Directory
- SSO - SAML
- Other
Configuration report
List of activated Apps
Nextcloud Signing status
Nextcloud Logs
Additional info
Environment
- Nextcloud 34.0.3, on-premise (AlmaLinux, Apache reverse proxy, MariaDB,
Redis for locking/distributed cache) - Reproduced consistently in a Talk deployment with multiple concurrent
participants receiving image attachments
Related
- #63513 / #63486 (self-healing mitigation, does not fix this root cause)
- #58787 (same underlying symptom pattern)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in Generator::savePreview() and trace PreviewMapper::insert(), storageFactory->deletePreview(), and getPreviewForSpecification(). Add coverage for concurrent generation of the same file and specification; done means the UNIQUE_CONSTRAINT_VIOLATION path preserves the shared file and returns the winner's persisted Preview record.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 62/100