Skip to content

🧵 Improve synchronization of connection_state transitions #494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 19, 2025
41 changes: 27 additions & 14 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down