Summary
Implement support for processing multiple IMAP commands from a single client connection in parallel, as permitted by RFC 3501 Section 5.5 ("Multiple Commands in Progress").
Currently, IMAPClientProxy.run() in user_server.py processes commands strictly in serial: read command, process command, send result, repeat. The per-mailbox infrastructure (Mailbox.management_task, task_queue, conflict detection in command_can_proceed) already supports parallelism across different clients, but this is never exercised within a single client connection.
Background
RFC 3501 §5.5 says:
The client MAY send another command without waiting for the completion result response of a command, subject to ambiguity rules and flow control constraints. Similarly, a server MAY begin processing another command before processing the current command to completion, subject to ambiguity rules.
Key constraints:
- Ambiguity: If commands would affect each other's results, the server MUST execute them to completion in order
- EXPUNGE protection: Server MUST NOT send untagged EXPUNGE while FETCH, STORE, or SEARCH are in progress
- Sequence number safety: Commands producing EXPUNGE must complete before commands using sequence numbers
Proposed Design
-
Split the serial loop: Replace the serial read-process loop in IMAPClientProxy.run() with a reader task that loops reading commands and dispatches worker tasks (one per in-flight command).
-
Worker tasks go through the existing cmd_processor.command() path, which enqueues into the mailbox's task_queue and blocks on ready. The existing conflict detection in command_can_proceed already handles RFC 3501 ambiguity rules.
-
Per-client write serialization: Add a per-client asyncio.Queue for outbound messages. All worker tasks and the notification system post to this queue. A single writer task drains it to the TCP connection, preventing interleaved partial responses.
-
Unsolicited response gating: The existing pending_notifications mechanism queues EXISTS/RECENT/EXPUNGE when a client is busy. With multiple commands in-flight, a single gatekeeper decides when unsolicited responses can be sent -- only between tagged responses, or via a dedicated queue so only one task sends them.
-
Per-client ordering for ambiguity: Extend command_can_proceed to track per-client command order so that command N+1 from the same client waits for command N if they conflict.
Key Files
asimap/user_server.py -- IMAPClientProxy.run() serial loop
asimap/client.py -- BaseClientHandler.command() dispatch and response
asimap/mbox.py -- Mailbox.management_task, command_can_proceed, task_queue
asimap/parse.py -- IMAPClientCommand.ready_and_okay() context manager
Tasks
Summary
Implement support for processing multiple IMAP commands from a single client connection in parallel, as permitted by RFC 3501 Section 5.5 ("Multiple Commands in Progress").
Currently,
IMAPClientProxy.run()inuser_server.pyprocesses commands strictly in serial: read command, process command, send result, repeat. The per-mailbox infrastructure (Mailbox.management_task,task_queue, conflict detection incommand_can_proceed) already supports parallelism across different clients, but this is never exercised within a single client connection.Background
RFC 3501 §5.5 says:
Key constraints:
Proposed Design
Split the serial loop: Replace the serial read-process loop in
IMAPClientProxy.run()with a reader task that loops reading commands and dispatches worker tasks (one per in-flight command).Worker tasks go through the existing
cmd_processor.command()path, which enqueues into the mailbox'stask_queueand blocks onready. The existing conflict detection incommand_can_proceedalready handles RFC 3501 ambiguity rules.Per-client write serialization: Add a per-client
asyncio.Queuefor outbound messages. All worker tasks and the notification system post to this queue. A single writer task drains it to the TCP connection, preventing interleaved partial responses.Unsolicited response gating: The existing
pending_notificationsmechanism queues EXISTS/RECENT/EXPUNGE when a client is busy. With multiple commands in-flight, a single gatekeeper decides when unsolicited responses can be sent -- only between tagged responses, or via a dedicated queue so only one task sends them.Per-client ordering for ambiguity: Extend
command_can_proceedto track per-client command order so that command N+1 from the same client waits for command N if they conflict.Key Files
asimap/user_server.py--IMAPClientProxy.run()serial loopasimap/client.py--BaseClientHandler.command()dispatch and responseasimap/mbox.py--Mailbox.management_task,command_can_proceed,task_queueasimap/parse.py--IMAPClientCommand.ready_and_okay()context managerTasks
IMAPClientProxy.run()into reader + dispatchercommand_can_proceedfor per-client ordering