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 +4 -4
- data/CHANGELOG.md +19 -0
- data/lib/nwc_ruby/transport/relay_connection.rb +58 -11
- data/lib/nwc_ruby/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b847b05f148d03753548528e0f4483129cf59887e40329f28e7bbe31f0eda1cd
|
|
4
|
+
data.tar.gz: 9c99704df81a6e7cfa9c1bfede014b49270b15bf11a3bba8663b5d6f1f3500fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
@
|
|
228
|
-
rescue
|
|
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
|
data/lib/nwc_ruby/version.rb
CHANGED