feat(imap): add waitForContinuation flag to idleStart (RFC 2177 hand-off support)#274
Merged
robert-virkus merged 1 commit intoApr 25, 2026
Merged
Conversation
Per RFC 2177 §3, IDLE mode is not active on the server until it has sent
the `+ idling` continuation response. Callers that plan to rely on this
guarantee (e.g. disconnect immediately after, hand off the TCP connection
to another process, or start parsing untagged responses externally) need
to wait for that response.
Today `idleStart()` returns a pre-completed Future<T>.value() right after
queueing the IDLE command, because the command task's completer is only
resolved on the tagged OK that follows DONE. This creates a race: on slow
networks the caller can wrap up and close the socket before `+ idling`
arrives, leaving the continuation in the peer's TCP buffer.
This change:
- Adds `idleStart({bool waitForContinuation = false})`. Default keeps the
existing immediate-return semantics for backward compatibility.
- When `waitForContinuation: true`, the returned future resolves on the
next continuation response received while in IDLE mode, and fails with
`ImapException` if the client disconnects or hits a connection error
before the continuation arrives.
- `onContinuationResponse` now resolves the pending completer when in
IDLE mode (previously it only logged a warning when not in IDLE mode).
- Cleanup added to `idleDone()`, `disconnect()` and `onConnectionError`
so callers never hang on the returned future.
Tests added for the new flag (resolves on `+ idling`; fails on disconnect).
All existing tests continue to pass.
Member
|
Thanks so much for your contribution! |
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.
Summary
Adds optional
waitForContinuationparameter toImapClient.idleStart()so callers can wait for the server's+ idlingcontinuation response (per RFC 2177 §3) before considering IDLE mode active on the server.Motivation
Current
idleStart()returns a pre-completedFuture<T>.value()right after queueing the IDLE command (imap_client.dart L2282-2286) — the task's completer is reserved foridleDone()(tagged OK after DONE). This creates a race when the caller plans to do something right afteridleStart()returns: the IDLE command may still be in flight, and imap's+ idlingarrives into a socket buffer the caller has already stopped reading.I hit this while building a WebSocket-IMAP proxy that hands off the TCP connection to a different process once the mobile client enters IDLE and disconnects: the proxy was receiving
+ idlingafter hand-off and mis-interpreting it as a real notification. RFC 2177 §3 explicitly states that "as long as an IDLE command is active, the server is now free to send untagged EXISTS, EXPUNGE, and other messages at any time" — i.e. IDLE isn't actually active from the server's perspective until it has sent the continuation.Changes
idleStart({bool waitForContinuation = false}). Default preserves existing semantics; backwards compatible.waitForContinuation: true, the returned future resolves whenonContinuationResponsereceives the+in IDLE mode.idleDone(),disconnect()andonConnectionErrorso the future never hangs if the continuation cannot arrive — it fails with anImapExceptioninstead.onContinuationResponsenow resolves the pending completer when in IDLE mode (previously only logged when not in IDLE mode).Tests
Two new tests (file
test/imap/imap_client_test.dart):ImapClient idle waitForContinuation resolves on + idling— happy path.ImapClient idle waitForContinuation fails on disconnect— future errors out whendisconnect()is called before the continuation arrives.All 51 existing tests in
imap_client_test.dartcontinue to pass locally (dart test test/imap/imap_client_test.dart --timeout=30s).Compatibility
idleStart()unchanged — no parameter required.waitForContinuation: true.🤖 Generated with Claude Code