COR-218 - A NOOP on a cold connection connects first - #115
Merged
Merged
Conversation
noop() returned success without sending anything when the session had no mailimap - never connected, or torn down. The client uses NOOP as its connectivity and credentials probe and as the keep-alive that holds one pooled connection warm; on a cold connection both were answered with a success that had checked nothing and connected nothing, and the first real command paid the handshake the keep-alive exists to avoid. NOOP now goes through loginIfNeeded() like every other command. The interrupt test's queued NOOP consequently reconnects to the silent endpoint and blocks in turn; it is interrupted like the command before it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…COR-218 unsetup() nilled mImap under the lock and set STATE_DISCONNECTED only after closing and freeing the stream, which can block on a TLS shutdown; a needsReconnect() on another thread called the session logged in meanwhile. The store moves next to the nil. Not testable deterministically: the window lies inside unsetup(), where no test hook reaches. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b161dcb. Configure here.
There was a problem hiding this comment.
🔵 Needs a closer look
The changes affect core IMAP connection lifecycle, reconnect behavior, and cross-thread teardown state, warranting final human review.
Pull request overview
Fixes IMAP lifecycle handling so NOOP connects cold sessions and teardown publishes disconnected state safely.
Changes:
- Routes NOOP through
loginIfNeeded(). - Publishes disconnected state during teardown.
- Updates connection and interruption tests.
File summaries
| File | Description |
|---|---|
unittest/IMAPInterruptCurrentCommandTests.swift |
Updates queued-operation interruption coverage. |
unittest/IMAPConnectionLeaseTests.swift |
Tests cold and reconnected NOOP behavior. |
src/core/imap/MCIMAPSession.cpp |
Fixes NOOP connection behavior and teardown state ordering. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Two pre-existing gaps found while fixing COR-211/217, both in
IMAPSession.noop()on a session with nomailimapreturned success without sending anything — never connected, or torn down byunsetup(). The client uses NOOP as its connectivity/credentials probe (check(),checkAuthStatus()) and as the keep-alive that holds one render-pool connection warm; on a cold connection both got a success that checked nothing and connected nothing, and the first real command paid the handshake. NOOP now goes throughloginIfNeeded()like every other command. Consequences to know about: the keep-alive re-logs in a connection that was torn down behind it within one tick (≤25 s), which on a LOGIN-rationed server (Yahoo) costs one LOGIN per deliberate teardown — that is what "warm" means; andcheck()on a cold pool can now returnErrorConnectionwhere it used to succeed vacuously, which is the probe doing its job. A LOGIN that fails is retried by the keep-alive as before — unchanged, since a failed login never tore the socket down.unsetup()publishedSTATE_DISCONNECTEDafter closing and freeing the stream, outside the lock under which it nilsmImap;needsReconnect()on another thread could call the session logged in while a TLS shutdown ran. The store moves next to the nil. Ordering-only; no deterministic test reaches insideunsetup().Tests.
testNoopConnectsAColdConnection: a NOOP on a never-connected lease connects (one client), then after a connection-scoped disconnect a NOOP reconnects (two clients). Against the old code: zero and one.testInterruptDoesNothingForAnOperationThatIsNotRunningadjusted: its queued NOOP now reconnects to the silent endpoint and blocks in turn, and is interrupted like the command before it. The cut test dropped its now-redundant explicit connect. 32 IMAP tests green, 30 under ASan.Both source hunks are in upstream code: a 3-line deletion and a one-statement move. New Windows prebuilt needed.
🤖 Generated with Claude Code
Note
Medium Risk
Changes core IMAP connection lifecycle for NOOP, keep-alive, and cross-thread disconnect visibility; behavior shifts for cold pools and LOGIN-rationed servers.
Overview
IMAP NOOP no longer returns immediately when there is no active
mailimaphandle. It now goes throughloginIfNeeded()like other commands, so connectivity checks, auth probes, and pool keep-alives actually open the socket and log in instead of reporting success without touching the server. After a deliberate teardown, the next NOOP reconnects within one keep-alive tick;check()on a cold pool can surfaceErrorConnectionwhere it used to succeed vacuously.unsetup()now setsSTATE_DISCONNECTEDunder the same lock as clearingmImap, before the stream is closed and freed, soneedsReconnect()on another thread cannot treat the session as still logged in during teardown.Tests add
testNoopConnectsAColdConnection, drop a redundant explicit connect in the cut/reconnect lease test, and extend the interrupt queue test so a queued NOOP reconnects and blocks until interrupted.Reviewed by Cursor Bugbot for commit b161dcb. Bugbot is set up for automated code reviews on this repo. Configure here.