nextcloud / nextcloud/server

[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")

Open
#64,138 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

0. Needs triage 34-feedback bug
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.