fix IMAPIdleHandler thread safety: pass idleTag via init, clean up event loop group on reconnect

This commit is contained in:
2026-03-14 14:20:53 +01:00
parent 55ec465677
commit 3b82e6cd95
2 changed files with 11 additions and 10 deletions

View File

@@ -67,6 +67,12 @@ public actor IMAPIdleClient {
// MARK: - Connection
private func connectAndLogin() async throws {
// Clean up previous connection if reconnecting
try? await channel?.close()
channel = nil
try? await group?.shutdownGracefully()
group = nil
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let sslContext = try NIOSSLContext(configuration: TLSConfiguration.makeClientConfiguration())
let hostname = host
@@ -138,16 +144,14 @@ public actor IMAPIdleClient {
// Iterative IDLE loop avoids unbounded stack growth from recursion
while !Task.isCancelled {
// Swap response handler for IDLE handler
let idleTag = "IDLE1"
let (stream, streamContinuation) = AsyncStream<IMAPIdleEvent>.makeStream()
let idleHandler = IMAPIdleHandler(continuation: streamContinuation)
let idleHandler = IMAPIdleHandler(continuation: streamContinuation, idleTag: idleTag)
let oldHandler = try await getResponseHandler()
try await pipeline.removeHandler(oldHandler).get()
try await pipeline.addHandler(idleHandler).get()
let idleTag = "IDLE1"
idleHandler.setIdleTag(idleTag)
// Send IDLE command
let idleCommand = TaggedCommand(tag: idleTag, command: .idleStart)
channel.writeAndFlush(IMAPClientHandler.Message.part(.tagged(idleCommand)), promise: nil)

View File

@@ -16,14 +16,11 @@ final class IMAPIdleHandler: ChannelInboundHandler, RemovableChannelHandler, @un
typealias InboundIn = Response
private let continuation: AsyncStream<IMAPIdleEvent>.Continuation
private var idleTag: String?
private let idleTag: String
init(continuation: AsyncStream<IMAPIdleEvent>.Continuation) {
init(continuation: AsyncStream<IMAPIdleEvent>.Continuation, idleTag: String) {
self.continuation = continuation
}
func setIdleTag(_ tag: String) {
idleTag = tag
self.idleTag = idleTag
}
func channelRead(context: ChannelHandlerContext, data: NIOAny) {