nwc-ruby 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5d73701179764ad26d1ad95d212e1abd5856788a951591e3f8489b16e66d0f0
4
- data.tar.gz: 97047cb484ab707715cd0ce4590d356a9b0e23a307729c808f0098f043c94dba
3
+ metadata.gz: b847b05f148d03753548528e0f4483129cf59887e40329f28e7bbe31f0eda1cd
4
+ data.tar.gz: 9c99704df81a6e7cfa9c1bfede014b49270b15bf11a3bba8663b5d6f1f3500fa
5
5
  SHA512:
6
- metadata.gz: f7bfd6655a81a49a9c04e534a872be4be1438af498b77f452a14706feb8535b9421745cc4f15c488339532019e1f94fb0b56d14c55a565b440f6232fea067b74
7
- data.tar.gz: 8af52b0b23683c1c62e0d64f4c02597c82c9aa64ae277da7d7099208453ba596db58b3762abdb728a08c2d063013befc39f8c699c39ce4e66aa921c77e90a839
6
+ metadata.gz: 11e62747c2dfa88459d900a2e1f89e7a42df5420b88221508bf62df39b65ebe7f9937db357557b2ab3ee8b95be0b50b5062fa3667cf21002dbf607366f49675d
7
+ data.tar.gz: c0ff4c40f5d8fa68d3b12e6b7c745ff1ff5b19da5719c0998c8cc5db6098ee77917c2c914b0ca96dc9e9301ac4240595f7e472019e6001394c996e75ffdcff6a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] — 2026-04-21
11
+
12
+ ### Fixed
13
+
14
+ - Ctrl+C / SIGINT / SIGTERM now reliably exits the notification listener. The
15
+ previous trap handler called `@conn.close` directly, which deadlocks because
16
+ closing an SSL socket from inside a Ruby signal handler is unsafe
17
+ (`docker stop` was the only way out).
18
+ - Replaced the trap-based close with a self-pipe: the trap only flips `@stop`
19
+ and writes one byte to a pipe; an `Async` task reads from the pipe inside the
20
+ reactor and calls `top.stop` from the correct fiber context. This avoids two
21
+ further async-specific pitfalls discovered along the way — `Async::Task#stop`
22
+ raises `ThreadError: can't be called from trap context` (uses a Mutex), and
23
+ offloading to `Thread.new { task.stop }` fails with `NoMethodError` on
24
+ `Fiber.scheduler` because the reactor lives on a different thread.
25
+ - Added `rescue Interrupt, Async::Stop` at the top of the reconnect-loop rescue
26
+ chain in `RelayConnection#run!` so the stop signal is not swallowed by the
27
+ generic reconnect-on-error path.
28
+
10
29
  ## [0.2.1] — 2026-04-20
11
30
 
12
31
  ### Fixed
@@ -63,14 +63,19 @@ module NwcRuby
63
63
 
64
64
  def stop!
65
65
  @stop = true
66
- # Close the websocket to unblock the read loop. The @stop flag
67
- # will cause the run loop to exit on its own. We avoid calling
68
- # @top_task.stop here because stop! may be called from a
69
- # different thread than the Async reactor.
70
- begin
71
- @conn&.close
72
- rescue StandardError
73
- nil
66
+ # Poke the signal pipe if available (works from any thread); the
67
+ # watcher task will call @top_task.stop from inside the reactor.
68
+ # If we're already inside the reactor thread/fiber, we can stop
69
+ # the top task directly.
70
+ if @signal_pipe_w
71
+ begin
72
+ @signal_pipe_w.write_nonblock('.')
73
+ rescue IO::WaitWritable, Errno::EPIPE, IOError
74
+ nil
75
+ end
76
+ else
77
+ task = @top_task
78
+ task&.stop
74
79
  end
75
80
  end
76
81
 
@@ -82,10 +87,15 @@ module NwcRuby
82
87
 
83
88
  Async do |top|
84
89
  @top_task = top
90
+ signal_watcher = start_signal_watcher(top)
85
91
  until @stop
86
92
  begin
87
93
  run_one_connection(top)
88
94
  backoff = 1
95
+ rescue Interrupt, Async::Stop
96
+ # Ctrl-C / SIGTERM / task.stop: exit cleanly.
97
+ @stop = true
98
+ break
89
99
  rescue StandardError => e
90
100
  break if @stop
91
101
 
@@ -97,7 +107,12 @@ module NwcRuby
97
107
  backoff *= 2
98
108
  end
99
109
  end
110
+ rescue Interrupt
111
+ # Signal arrived while not inside a connection; just exit.
112
+ @stop = true
100
113
  ensure
114
+ signal_watcher&.stop
115
+ close_signal_pipe
101
116
  @top_task = nil
102
117
  end
103
118
  end
@@ -219,19 +234,51 @@ module NwcRuby
219
234
  end
220
235
 
221
236
  def install_traps
237
+ # Keep signal handlers tiny. We can't do much from inside a Ruby
238
+ # signal handler:
239
+ # - SSL I/O / socket close can deadlock.
240
+ # - Async::Task#stop takes a Mutex, which raises
241
+ # "can't be called from trap context (ThreadError)".
242
+ # - Thread.new { task.stop } runs outside the reactor and
243
+ # Fiber.scheduler is nil there.
244
+ # So: flip a flag and poke a self-pipe. An Async task watches the
245
+ # read end of the pipe and calls @top_task.stop from inside the
246
+ # reactor, which is the only safe place to do it.
247
+ @signal_pipe_r, @signal_pipe_w = IO.pipe
222
248
  %w[TERM INT].each do |sig|
223
249
  trap(sig) do
224
250
  @stop = true
225
- # Close the socket to unblock the read loop.
226
251
  begin
227
- @conn&.close
228
- rescue StandardError
252
+ @signal_pipe_w.write_nonblock('.')
253
+ rescue IO::WaitWritable, Errno::EPIPE, IOError
229
254
  nil
230
255
  end
231
256
  end
232
257
  end
233
258
  end
234
259
 
260
+ def start_signal_watcher(top)
261
+ return unless @signal_pipe_r
262
+
263
+ top.async do
264
+ @signal_pipe_r.read(1)
265
+ @logger.debug('[nwc] signal received, stopping')
266
+ top.stop
267
+ rescue IOError, Errno::EBADF
268
+ nil
269
+ end
270
+ end
271
+
272
+ def close_signal_pipe
273
+ [@signal_pipe_r, @signal_pipe_w].each do |io|
274
+ io&.close
275
+ rescue IOError
276
+ nil
277
+ end
278
+ @signal_pipe_r = nil
279
+ @signal_pipe_w = nil
280
+ end
281
+
235
282
  def default_logger
236
283
  logger = Logger.new($stdout)
237
284
  logger.level = ENV['NWC_LOG_LEVEL'] ? Logger.const_get(ENV['NWC_LOG_LEVEL'].upcase) : Logger::INFO
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NwcRuby
4
- VERSION = '0.2.1'
4
+ VERSION = '0.2.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nwc-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - MegalithicBTC