thunderbird / thunderbird/thunderbird-android
`deleteMessages` holds a write transaction for 39 s, blocking the controller queue and all DB readers
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 14k
- Forks
- 2.8k
- Avg merge
- 3d 3h
- Merged PRs (30d)
- 57
Description
Checklist
- I have used the search function to see if someone else has already submitted the same bug report.
- I will describe the problem with as much detail as possible.
App
Thunderbird for Android
App version
22.0
Where did you get the app from?
F-Droid
Android version
12
Device model
OnePlus 13
Steps to reproduce
Delete a message from the inbox over IMAP (Cyrus IMAP < 3.0 with no MOVE extension).
Expected behavior
This should work flawlessly.
Actual behavior
The app either hangs or is sluggish.
claude-opus-5 analysis:
Deleting one INBOX message (account with no trash folder) stalled MessagingController's single command thread:
08:44:04.736 Running command 'deleteMessages', seq = 27 (background priority)
08:44:04.737 Not moving deleted messages to local Trash folder. Removing local copies.
(tid 26870 silent until the log ends at 08:44:43.416)
runInBackground() logs completion or error unconditionally, so the thread never returned.
LockableDatabase's Transaction ended line never appeared (debug logging was on), nor did
Log.d("Delete policy for account …") at MessagingController.java:2147 — so it is stuck inside
localFolder.setFlags(…, DELETED, …), line 2105.
The pool holds one connection, so every other reader starved:
08:44:34.746 W SQLiteConnectionPool: … unable to grant a connection to thread 121108
(ForkJoinPool.commonPool-worker-3) with flags 0x1 for 30.000002 seconds.
Connections: 0 active, 1 idle, 0 available.
… MessageListRemoteViewFactory.onDataSetChanged → MessageListLoader.getMessageListInfo
→ DefaultMessageListRepository.getThreadedMessages → LockableDatabase.execute
Suspected cause: LocalMessage.setFlag()/delete() call localStore.notifyChange()
(LocalMessage.java:291, 334) inside the transaction opened by LocalFolder.setFlags
(LocalFolder.java:853). DefaultMessageListRepository notifies listeners synchronously, so
MessageListWidgetManager reloads the widget, which queries the locked database — a lock-order
inversion.
Knock-on: a checkMail wake lock was taken at 08:44:06.502 for a command that never ran;
queueDelete/processPendingCommands (lines 2168/2176) were never reached, so no
UID STORE \Deleted was sent. The message vanished server-side only after an app restart.
Logs
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
Reproduce deletion over IMAP with Cyrus IMAP lacking MOVE, then inspect LocalFolder.java:853 and LocalMessage.java:291,334 around transaction and notification handling. Trace the synchronous reload through DefaultMessageListRepository and MessageListWidgetManager, and verify that deletion completes, UID STORE is sent, and database readers are not blocked.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin, sqlite
- Domain
- databases, mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100