Skip to content

Commit 95cd5bb

Browse files
committed
another imap idle trial
1 parent 42de934 commit 95cd5bb

4 files changed

Lines changed: 25 additions & 16 deletions

File tree

app/services/imap/gmail_client.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff 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!

app/services/imap_idle_runner.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff 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?

script/imap_idle.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,27 @@
4343

4444
runner = 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!
5355
end
5456

5557
logger.info("[imap_idle] Starting IMAP IDLE runner for #{label}") if logger
5658

5759
begin
58-
# Infinite loop: use stop! via signal to exit
5960
runner.run
6061
rescue SystemExit, Interrupt
6162
# normal exit
6263
rescue => e
6364
logger.error("[imap_idle] Fatal error: #{e.class}: #{e.message}") if logger
6465
raise
6566
ensure
67+
stop_w.close rescue nil
6668
logger.info('[imap_idle] Exiting') if logger
6769
end

spec/services/imap/gmail_client_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
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))

0 commit comments

Comments
 (0)