Text never releases its file lock when an editing session ends abnormally
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 659
- Forks
- 133
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 110
Description
Describe the bug
When a document is opened in Text, the app acquires a lock through files_lock. If the editing session ends abnormally — tab closed without a clean teardown, browser crash, session expiry — the lock is never released.
The failure is silent. A desktop client holding a newer local copy retries the upload and gets HTTP 423 Locked forever, with no actionable error surfaced in the web UI or in the client. Files whose local copy happens to be unchanged simply stop syncing in both directions, with no visible sign at all.
Orphan rows are also left behind in oc_text_sessions, with last_contact frozen at the moment the session died. Every stale lock we found had exactly one matching stale session, which suggests both are leaked by the same teardown path.
To Reproduce
- Open a Markdown file in Text.
- Kill the session abruptly (close the tab while editing, or let the session expire).
- Inspect
oc_files_lock: a row remains, withowner = "Text"and a negativettl. - On a desktop client syncing that folder, modify the same file locally.
- Watch the web server access log:
PUT /remote.php/dav/files/<user>/<path>→423, repeating indefinitely.
Expected behavior
The lock is released when the editing session ends, or at the latest when the stale session is garbage-collected.
Observed on a production instance
- 8 stale locks accumulated over 3 weeks, all
owner = "Text", the oldest 20 days old. - Each had a matching orphan row in
oc_text_sessions. - One file had a newer local copy: its desktop client issued a
PUTevery 2 hours and received423each time, for 19 consecutive days. - The 7 other files were silently out of sync — discovered only while investigating the first one.
Why nothing cleans this up
files_lock ships a cleanup job, OCA\FilesLock\Cron\Unlock, but with default settings it is a no-op:
ConfigServicedefaultslock_timeoutto'-1';FileLock::ETA_INFINITE === -1;LockService::getDeprecatedLocks()returns[]when the timeout equalsETA_INFINITE, so the job deletes nothing.
Setting a finite timeout re-arms the job and is an effective workaround:
occ config:app:set files_lock lock_timeout --value=1440
This bounds the damage but does not fix the leak: the lock is still orphaned, and the file stays unsyncable until the job runs.
Possible second issue, in files_lock
Even with a finite lock_timeout, expiry does not track activity. LocksRequest::update() — the "extending existing lock" path — writes token, ttl, user_id, owner and scope, but not creation, while LocksRequest::getLocksOlderThan() filters on creation. A lock actively extended by an ongoing session can therefore be purged based on its original creation time, so a long editing session may lose its lock mid-edit. This is why we chose a deliberately large timeout above.
Happy to split this into a separate issue against files_lock if you prefer.
Server details:
- Nextcloud version: 34.0.3 (also observed on 32.x and 33.x before upgrading)
- PHP Version: 8.4
- Database: PostgreSQL 17.10 and MariaDB 11.8.6 — two separate instances, both affected
- files_lock: 34.0.1
Client details:
- OS: Debian (server), Linux desktop
- Desktop sync client: 33.0.7
- Browser: Firefox
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 by reproducing the abrupt-session scenario and trace Text's teardown and stale-session garbage-collection paths. Then inspect files_lock's Cron\Unlock, ConfigService, FileLock, LockService::getDeprecatedLocks(), and LocksRequest::update() and getLocksOlderThan(). Done means stale sessions and their locks are released without breaking active locks or leaving clients stuck with HTTP 423.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, mariadb, php, postgresql
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100