diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 278913a7..2d005c92 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1131,28 +1131,27 @@ 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. + # # Related: #logout, #logout! def disconnect + in_logout_state = try_state_logout? return if disconnected? - state_logout! begin - begin - # try to call SSL::SSLSocket#io. - @sock.io.shutdown - rescue NoMethodError - # @sock is not an SSL::SSLSocket. - @sock.shutdown - end + @sock.to_io.shutdown rescue Errno::ENOTCONN # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms. rescue Exception => e @receiver_thread.raise(e) end + @sock.close @receiver_thread.join - synchronize do - @sock.close - end raise e if e + ensure + # Try again after shutting down the receiver thread. With no reciever + # left to wait for, any remaining locks should be _very_ brief. + state_logout! unless in_logout_state end # Returns true if disconnected from the server. @@ -3363,8 +3362,6 @@ def start_receiver_thread rescue Exception => ex @receiver_thread_exception = ex # don't exit the thread with an exception - ensure - state_logout! end end @@ -3446,6 +3443,8 @@ def receive_responses @idle_done_cond.signal end end + ensure + state_logout! end def get_tagged_response(tag, cmd, timeout = nil) @@ -3808,15 +3807,29 @@ def state_selected! end def state_unselected! - state_authenticated! if connection_state.to_sym == :selected + synchronize do + state_authenticated! if connection_state.to_sym == :selected + end end def state_logout! + return true if connection_state in [:logout, *] synchronize do + return true if connection_state in [:logout, *] @connection_state = ConnectionState::Logout.new end end + # don't wait to aqcuire the lock + def try_state_logout? + return true if connection_state in [:logout, *] + return false unless acquired_lock = mon_try_enter + state_logout! + true + ensure + mon_exit if acquired_lock + end + def sasl_adapter SASLAdapter.new(self, &method(:send_command_with_continuations)) end