- Shipped
- August 12, 2026 at 3:20 PM UTC
- Author
- Kamo
- Commit
- c2e8cfe
Interrupting the IDLE thread cannot end an IDLE: it is parked in a blocking socket read that only sees the flag once the read returns on its own, up to Dovecot's idle timeout later. Its registration was dropped synchronously anyway, so the next subscribe opened a second connection beside the still-live first, and the departing thread later deregistered whichever session had replaced it — letting a third start for a mailbox that already had one. Sage@KamoCRM.com reached five concurrent IDLE connections against Dovecot's mail_max_userip_connections=10, after which the mailbox stopped answering and the nav-badge unread read failed with "[UNAVAILABLE] Maximum number of connections from user+IP exceeded", publishing nothing. ImapIdleRegistry now owns that lifecycle: one session per mailbox, claimed before the connect rather than from inside the submitted task; a teardown that closes the Store — off the caller, since close() can block on the message-cache lock the idling thread holds; and an identity-checked release. The live map is the count, so the cap behind MAX_IDLE_CONNECTIONS cannot drift and silently disable IDLE pod-wide. EmailUnreadPublisher retries a failed read up to three times. Nothing polls the count and the 60s watcher sweep deliberately skips mailboxes with an IDLE listener, so one dropped read left the badge stale until the next mailbox event.