File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -122,17 +122,18 @@ def idle_once(timeout: 1500)
122122 begin
123123 if @imap . respond_to? ( :idle )
124124 # Break IDLE promptly on first response so the caller can FETCH.
125- # Response handlers run outside the monitor in the receiver thread,
126- # and idle's wait releases the monitor, so idle_done can be called
127- # directly without deadlock. Avoids a detached Thread that can
128- # outlive the connection and hit a torn-down monitor.
125+ # idle_done must be called from a separate thread: the response
126+ # handler runs inside the receiver thread's monitor lock, so a
127+ # direct call would deadlock or extend the lock window and race
128+ # with signal-driven shutdown. rescue nil handles the case where
129+ # the connection is torn down before the thread runs.
129130 idle_done_called = false
130131 @imap . idle ( timeout ) do |resp |
131132 got_activity = true
132133 yield resp if block_given?
133134 unless idle_done_called
134135 idle_done_called = true
135- @imap . idle_done
136+ Thread . new { @imap . idle_done rescue nil }
136137 end
137138 end
138139 else
@@ -159,6 +160,11 @@ def idle_once(timeout: 1500)
159160 got_activity ? :activity : :timeout
160161 end
161162
163+ def interrupt_idle!
164+ @imap &.idle_done
165+ rescue StandardError
166+ end
167+
162168 private
163169
164170 def ensure_imap!
Original file line number Diff line number Diff line change @@ -194,10 +194,7 @@ def short_error(e)
194194
195195 def stop!
196196 @stop = true
197- begin
198- disconnect!
199- rescue StandardError
200- end
197+ @client &.interrupt_idle!
201198 end
202199
203200 def respond_to_logger_info?
Original file line number Diff line number Diff line change 4343
4444runner = ImapIdleRunner . new ( label : label , logger : logger )
4545
46- Signal . trap ( 'TERM' ) do
47- logger . info ( '[imap_idle] SIGTERM received, stopping...' ) if logger
48- runner . stop!
49- end
50- Signal . trap ( 'INT' ) do
51- logger . info ( '[imap_idle] SIGINT received, stopping...' ) if logger
46+ stop_r , stop_w = IO . pipe
47+ Signal . trap ( 'TERM' ) { stop_w . write_nonblock ( "\0 " ) rescue nil }
48+ Signal . trap ( 'INT' ) { stop_w . write_nonblock ( "\0 " ) rescue nil }
49+
50+ Thread . new do
51+ IO . select ( [ stop_r ] )
52+ stop_r . close
53+ logger . info ( '[imap_idle] Signal received, stopping...' ) if logger
5254 runner . stop!
5355end
5456
5557logger . info ( "[imap_idle] Starting IMAP IDLE runner for #{ label } " ) if logger
5658
5759begin
58- # Infinite loop: use stop! via signal to exit
5960 runner . run
6061rescue SystemExit , Interrupt
6162 # normal exit
6263rescue => e
6364 logger . error ( "[imap_idle] Fatal error: #{ e . class } : #{ e . message } " ) if logger
6465 raise
6566ensure
67+ stop_w . close rescue nil
6668 logger . info ( '[imap_idle] Exiting' ) if logger
6769end
Original file line number Diff line number Diff line change 4949 # Simulate Net::IMAP#idle with yielding one response
5050 allow ( imap_double ) . to receive ( :respond_to? ) . with ( :idle ) . and_return ( true )
5151 allow ( imap_double ) . to receive ( :idle_done )
52+ allow ( Thread ) . to receive ( :new ) . and_wrap_original do |m , *args , &blk |
53+ blk . call
54+ double ( 'Thread' , join : true )
55+ end
5256 yielded = [ ]
5357 expect ( imap_double ) . to receive ( :idle ) do |timeout , &blk |
5458 blk . call ( double ( 'Resp' , name : 'EXISTS' , data : 1 ) )
You can’t perform that action at this time.
0 commit comments