From aaa6879923074784e53f5818fda232ca1ca79714 Mon Sep 17 00:00:00 2001 From: nick evans Date: Thu, 4 Jun 2026 09:09:38 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B5=20Fix=20deadlock=20in=20`#disconne?= =?UTF-8?q?ct`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `#disconnect` could deadlock when called inside the mutex, because the receiver thread could be signaling a condition variable and it would thus be unable to resume until the current thread releases its lock. Also, `#disconnect` shouldn't raise `IO#shutdown` exceptions on the receiver thread prior to closing the socket, when it is being called from the receiver thread. The exception is still raised, _after_ the socket is closed. --- lib/net/imap.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 12a4964d..8d21f400 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1190,22 +1190,24 @@ def tls_verified?; @tls_verified end # Disconnects from the server. # - # Waits for receiver thread to close before returning. Slow or stuck - # response handlers can cause #disconnect to hang until they complete. + # Waits for receiver thread to close before returning, except when called + # from inside the connection mutex such as from a response handler. Slow or + # stuck response handlers can cause #disconnect to hang until they complete. # # Related: #logout, #logout! def disconnect in_logout_state = try_state_logout? return if disconnected? + in_receiver_thread = Thread.current == @receiver_thread begin @sock.to_io.shutdown rescue Errno::ENOTCONN # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms. rescue Exception => e - @receiver_thread.raise(e) + @receiver_thread.raise(e) unless in_receiver_thread end @sock.close - @receiver_thread.join + @receiver_thread.join unless mon_owned? || in_receiver_thread raise e if e ensure # Try again after shutting down the receiver thread. With no reciever