{"record":{"id":"d066fdabf9d77c70","repo":"jstedfast/MailKit","slug":"the-imap-command-queue-is-empty","errorCode":null,"errorMessage":"The IMAP command queue is empty.","messagePattern":"The IMAP command queue is empty\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"MailKit/Net/Imap/ImapEngine.cs","lineNumber":3133,"sourceCode":"\n\t\t\t\t\ttoken = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);\n\t\t\t\t\tAssertToken (token, ImapTokenType.Eoln, \"Syntax error in untagged LIST response. {0}\", token);\n\t\t\t\t} else if (atom.Equals (\"VANISHED\", StringComparison.OrdinalIgnoreCase) && folder != null) {\n\t\t\t\t\tawait folder.OnVanishedAsync (this, cancellationToken).ConfigureAwait (false);\n\t\t\t\t\tawait SkipLineAsync (cancellationToken).ConfigureAwait (false);\n\t\t\t\t} else {\n\t\t\t\t\t// don't know how to handle this... eat it?\n\t\t\t\t\tawait SkipLineAsync (cancellationToken).ConfigureAwait (false);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t[MemberNotNull (nameof (current))]\n\t\tvoid PopNextCommand ()\n\t\t{\n\t\t\tlock (queue) {\n\t\t\t\tif (queue.Count == 0)\n\t\t\t\t\tthrow new InvalidOperationException (\"The IMAP command queue is empty.\");\n\n\t\t\t\tif (IsBusy)\n\t\t\t\t\tthrow new InvalidOperationException (\"The ImapClient is currently busy processing a command in another thread. Lock the SyncRoot property to properly synchronize your threads.\");\n\n\t\t\t\tcurrent = queue[0];\n\t\t\t\tqueue.RemoveAt (0);\n\n\t\t\t\ttry {\n\t\t\t\t\tcurrent.CancellationToken.ThrowIfCancellationRequested ();\n\t\t\t\t} catch {\n\t\t\t\t\tqueue.RemoveAll (x => x.CancellationToken.IsCancellationRequested);\n\t\t\t\t\tcurrent = null;\n\t\t\t\t\tthrow;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/// <summary>","sourceCodeStart":3115,"sourceCodeEnd":3151,"githubUrl":"https://github.com/jstedfast/MailKit/blob/9d3859a7855e3e17582c07fd01972b8e262bf176/MailKit/Net/Imap/ImapEngine.cs#L3115-L3151","documentation":"ImapEngine.PopNextCommand() throws this InvalidOperationException when it is asked to dequeue the next command to run but the internal command queue has zero entries. This is an internal synchronization invariant of the IMAP pipeline: a thread signaled the engine to run a command, but nothing was enqueued. It almost always indicates a race condition in caller thread usage of ImapClient.","triggerScenarios":"Calling ImapClient send/authenticate/folder APIs concurrently from multiple threads without synchronizing on ImapClient.SyncRoot; calling methods after another thread has already drained the queue; mixing Sync and Async calls on the same client from different threads.","commonSituations":"Multi-threaded apps (e.g. background workers plus UI threads) sharing a single ImapClient instance; a keepalive/NOOP thread racing a fetch thread; incorrectly hand-rolled synchronization instead of locking SyncRoot.","solutions":["Wrap every ImapClient call sequence in lock (imapClient.SyncRoot) { ... } so only one thread enqueues and processes commands at a time","Use one thread/task per ImapClient, or create a separate ImapClient (and connection) per thread","Check for double-consume bugs: ensure no code path signals/dispatches the queue more than once per enqueued command","Upgrade MailKit - older versions had queue races; newer releases serialize via the engine task"],"exampleFix":"// before\nawait imapClient.Inbox.GetMessageAsync(id);\nTask.Run(() => imapClient.Noop()); // unsynchronized\n\n// after\nlock (imapClient.SyncRoot) {\n    imapClient.Noop();\n}\nawait imapClient.Inbox.GetMessageAsync(id);","handlingStrategy":"try-catch","validationCode":"if (queueIsEmptySignaled && !clientIsBusy) {\n    // safe to proceed; otherwise synchronize first\n}\nlock (client.SyncRoot) {\n    bool busy = client.IsBusy;\n}","typeGuard":"bool CanIssueCommand(ImapClient client) {\n    lock (client.SyncRoot) { return !client.IsBusy && client.IsConnected && client.IsAuthenticated; }\n}","tryCatchPattern":"lock (client.SyncRoot) {\n    try {\n        // IMAP operations here\n    } catch (InvalidOperationException ex) when (ex.Message.Contains(\"command queue is empty\") || ex.Message.Contains(\"currently busy\")) {\n        // log and re-serialize access; do not retry blindly\n    }\n}","preventionTips":["Always lock SyncRoot around Sync API usage","Prefer async APIs which serialize internally","One client per thread of work"],"tags":["concurrency","imap","race-condition","threading"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9d3859a7855e3e17582c07fd01972b8e262bf176","analyzedAt":"2026-09-15T15:46:11.592Z","contentChangedAt":"2026-09-15T15:46:11.592Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}