Skip to content

Support parallel IMAP command processing per RFC 3501 §5.5 #483

Description

@scanner

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

  1. 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).

  2. 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.

  3. 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.

  4. 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.

  5. 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

  • Design per-client write queue and writer task
  • Refactor IMAPClientProxy.run() into reader + dispatcher
  • Worker task lifecycle (spawn, track, cleanup on disconnect)
  • Extend command_can_proceed for per-client ordering
  • Unsolicited response gating across parallel commands
  • Handle edge cases: client disconnect mid-parallel, timeouts
  • Integration tests for parallel command scenarios
  • Test with real IMAP clients (iOS Mail, Thunderbird, etc.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions