Skip to content

Commit 4cb6517

Browse files
Fix IMAP connection exhaustion under burst of searches
IMAP read/mutation operations opened a fresh connection per call and closed it via Dispose() without a graceful LOGOUT. Lingering connections plus repeated AUTHENTICATE attempts hit Gmail's per-account connection limit/throttle, stalling subsequent AUTHENTICATE calls so rapid searches hung for the full timeout. Reuse a single authenticated IMAP connection per account behind a per-account gate, with NOOP health-check, reconnect-and-retry on a broken reused connection, and graceful DisconnectAsync(true) on eviction/shutdown via IAsyncDisposable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ab78887 commit 4cb6517

3 files changed

Lines changed: 347 additions & 111 deletions

File tree

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<Project>
22
<PropertyGroup>
3-
<VersionPrefix>1.4.0</VersionPrefix>
3+
<VersionPrefix>1.4.1</VersionPrefix>
44
</PropertyGroup>
55
</Project>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# 2026-06-09: IMAP Connection Reuse & Graceful Disconnect
2+
3+
## Summary
4+
5+
Fixes a bug where rapid, repeated IMAP operations (e.g. a burst of
6+
`search_emails` calls) would hang for the full request timeout against Gmail.
7+
8+
Every IMAP operation previously opened a brand-new connection, ran a fresh
9+
`AUTHENTICATE`, and then closed the connection by `Dispose()` **without** a
10+
graceful `LOGOUT`. Abruptly-dropped connections linger server-side and count
11+
against Gmail's per-account simultaneous-connection limit, and Gmail throttles
12+
repeated `AUTHENTICATE` attempts. Under a burst of searches the limit/throttle
13+
kicked in and stalled the next `AUTHENTICATE`, so each subsequent call hung. SMTP
14+
(`send_email`) was unaffected because it already disconnected gracefully and uses
15+
a different protocol with different limits.
16+
17+
## Fix
18+
19+
`ImapProviderService` now keeps a single authenticated IMAP connection **per
20+
account** and reuses it across calls:
21+
22+
- A per-account `SemaphoreSlim` gate serializes access (an `ImapClient` runs one
23+
command at a time).
24+
- Each call health-checks the cached connection with a lightweight `NOOP` and
25+
transparently reconnects if it is dead, with a single reconnect-and-retry if a
26+
reused connection breaks mid-operation.
27+
- Connections are disconnected **gracefully** (`DisconnectAsync(true)`
28+
`LOGOUT`) when evicted or on service shutdown, so the server releases the slot
29+
immediately.
30+
- `ImapProviderService` implements `IAsyncDisposable`/`IDisposable` so the DI
31+
container tears down pooled connections cleanly.
32+
33+
This eliminates the repeated `AUTHENTICATE` round-trips that Gmail throttles and
34+
prevents connection-slot exhaustion.
35+
36+
## Scope
37+
38+
Read and mutating IMAP operations now share the pooled connection:
39+
`get_emails`, `search_emails`, `get_email_details`, `get_email_attachment`,
40+
`delete_email`, `mark_email_as_read`, `move_email`, and the APPEND-to-Sent step
41+
of `send_email`.
42+
43+
## Backward compatibility
44+
45+
No API or behavior changes for callers. All existing tests pass.

0 commit comments

Comments
 (0)