fix(search): keep searchMessages cooperative and bounded (fixes main-thread freezes/timeouts on large folders)#161
Draft
GoeppertVB wants to merge 1 commit into
Conversation
searchMessages enumerated message headers synchronously on Thunderbird's
main thread and recursed every subfolder, so a large folder (or an
unscoped whole-account search) froze the UI and ran past the bridge's 30s
request timeout — occasionally taking TB down with it. Cost scaled with
folder size, not match count: maxResults only trimmed the final slice and
date filters didn't prune the scan, so a rare-term query over a 200k-message
folder read every header.
Make the scan cooperative:
- yield to the event loop every SEARCH_YIELD_EVERY (2000) headers via
Services.tm.dispatchToMainThread so the UI stays responsive
- abort after SEARCH_TIME_BUDGET_MS (20s, under the 30s bridge timeout) and
return partial results flagged { truncated: true, message } instead of
hanging or erroring
- only force an IMAP updateFolder sync on the explicitly-requested folder,
not on every folder of a recursive/whole-account scan (updateFolder storm)
Matching semantics and the result shape on complete searches (bare array /
paginated object) are unchanged. Existing test suite passes (388 tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
searchMessages(the default, non-searchBodypath) can freeze the Thunderbird UI and time out — and occasionally crash TB — on large mailstores. In testing against an IMAP account with a ~191k-message INBOX and a ~219k-message All Mail folder, most searches returnedBridge error: Request to Thunderbird timed out; scoping to a small folder returned instantly.Root cause
The search is a synchronous linear scan on TB's main thread (
extension/mcp_server/api.js,searchFolder→db.enumerateMessages()), recursing every subfolder. Cost scales with folder size, not match count:maxResultsonly trims the returned slice — it doesn't bound the scan.startDate/endDatejustcontinuepast non-matches — they don't prune enumeration.SEARCH_COLLECTION_CAP(10 000 matches), so a rare-term query never trips it and reads every header (MIME-decoding subject/author/recipients each).refreshImapFolderSynccallsfolder.updateFolder()on every folder it visits, so an unscoped search also fires anupdateFolderstorm across the whole tree.Because the loop never yields, the UI is frozen the whole time; the bridge gives up at its 30 s
REQUEST_TIMEOUTwhile TB keeps churning, and opening a 200k-messagemsgDatabase+ building a large result array can tip a long-lived instance into an OOM/hang.Fix
Make the scan cooperative and bounded, without changing matching semantics:
SEARCH_YIELD_EVERY(2000) enumerated headers viaServices.tm.dispatchToMainThread, so the UI stays responsive.SEARCH_TIME_BUDGET_MS(20 s, safely under the 30 s bridge timeout) and return partial results flagged{ truncated: true, message }instead of hanging or erroring. The same flag is now surfaced when the pre-existing 10 000-match collection cap is hit (previously a silent truncation).updateFolderstorm: only force an IMAP sync on the explicitly requested folder, not on every folder of a recursive/whole-account scan.searchMessagesbecomesasync(its sole caller alreadyawaits it).Compatibility
offset, paginated object withoffset). Only incomplete searches change shape — they return{ messages, truncated: true, message, ... }so the partial-result signal always rides with the data.Testing
node --test test/*.cjs→ 388 tests, 0 failures (17 skipped, pre-existing).node --checkclean onapi.jsandmcp-bridge.cjs.Possible follow-ups (not in this PR)
enumerateMessages()pattern appears ingetRecentMessagesand a few other handlers; they could take the same cooperative treatment.🤖 Generated with Claude Code