thunderbird / thunderbird/thunderbird-android

`deleteMessages` holds a write transaction for 39 s, blocking the controller queue and all DB readers

Open
#11,542 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: performance
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

k9mail-logs_4.redacted.txt

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

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.