mt-libuv 4.1.0

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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.gitmodules +6 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +24 -0
  6. data/Gemfile +9 -0
  7. data/LICENSE +24 -0
  8. data/README.md +195 -0
  9. data/Rakefile +31 -0
  10. data/ext/README.md +6 -0
  11. data/ext/Rakefile +28 -0
  12. data/lib/mt-libuv/async.rb +51 -0
  13. data/lib/mt-libuv/check.rb +59 -0
  14. data/lib/mt-libuv/coroutines.rb +79 -0
  15. data/lib/mt-libuv/dns.rb +98 -0
  16. data/lib/mt-libuv/error.rb +88 -0
  17. data/lib/mt-libuv/ext/ext.rb +322 -0
  18. data/lib/mt-libuv/ext/platform/darwin_x64.rb +61 -0
  19. data/lib/mt-libuv/ext/platform/unix.rb +69 -0
  20. data/lib/mt-libuv/ext/platform/windows.rb +83 -0
  21. data/lib/mt-libuv/ext/tasks/mac.rb +24 -0
  22. data/lib/mt-libuv/ext/tasks/unix.rb +42 -0
  23. data/lib/mt-libuv/ext/tasks/win.rb +29 -0
  24. data/lib/mt-libuv/ext/tasks.rb +27 -0
  25. data/lib/mt-libuv/ext/types.rb +253 -0
  26. data/lib/mt-libuv/fiber_pool.rb +83 -0
  27. data/lib/mt-libuv/file.rb +309 -0
  28. data/lib/mt-libuv/filesystem.rb +263 -0
  29. data/lib/mt-libuv/fs_event.rb +37 -0
  30. data/lib/mt-libuv/handle.rb +108 -0
  31. data/lib/mt-libuv/idle.rb +59 -0
  32. data/lib/mt-libuv/mixins/accessors.rb +41 -0
  33. data/lib/mt-libuv/mixins/assertions.rb +25 -0
  34. data/lib/mt-libuv/mixins/fs_checks.rb +96 -0
  35. data/lib/mt-libuv/mixins/listener.rb +69 -0
  36. data/lib/mt-libuv/mixins/net.rb +42 -0
  37. data/lib/mt-libuv/mixins/resource.rb +30 -0
  38. data/lib/mt-libuv/mixins/stream.rb +276 -0
  39. data/lib/mt-libuv/pipe.rb +217 -0
  40. data/lib/mt-libuv/prepare.rb +59 -0
  41. data/lib/mt-libuv/q.rb +475 -0
  42. data/lib/mt-libuv/reactor.rb +567 -0
  43. data/lib/mt-libuv/signal.rb +62 -0
  44. data/lib/mt-libuv/spawn.rb +113 -0
  45. data/lib/mt-libuv/tcp.rb +465 -0
  46. data/lib/mt-libuv/timer.rb +107 -0
  47. data/lib/mt-libuv/tty.rb +42 -0
  48. data/lib/mt-libuv/udp.rb +302 -0
  49. data/lib/mt-libuv/version.rb +5 -0
  50. data/lib/mt-libuv/work.rb +86 -0
  51. data/lib/mt-libuv.rb +80 -0
  52. data/mt-libuv.gemspec +62 -0
  53. data/spec/async_spec.rb +67 -0
  54. data/spec/coroutines_spec.rb +121 -0
  55. data/spec/cpu_spec.rb +10 -0
  56. data/spec/defer_spec.rb +906 -0
  57. data/spec/dns_spec.rb +110 -0
  58. data/spec/dsl_spec.rb +43 -0
  59. data/spec/filesystem_spec.rb +270 -0
  60. data/spec/idle_spec.rb +44 -0
  61. data/spec/pipe_spec.rb +151 -0
  62. data/spec/spawn_spec.rb +119 -0
  63. data/spec/tcp_spec.rb +272 -0
  64. data/spec/test.sh +4 -0
  65. data/spec/test_fail.sh +3 -0
  66. data/spec/test_read.sh +3 -0
  67. data/spec/timer_spec.rb +14 -0
  68. data/spec/udp_spec.rb +73 -0
  69. data/spec/zen_spec.rb +34 -0
  70. metadata +196 -0
@@ -0,0 +1,567 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ module MTLibuv
6
+ class Reactor
7
+ include Resource, Assertions
8
+ extend Accessors
9
+
10
+
11
+ LIBUV_MIN_POOL = ENV['LIBUV_MIN_POOL'] || 8
12
+ LIBUV_MAX_POOL = ENV['LIBUV_MAX_POOL'] || 40
13
+ LIBUV_MAX_QUEUE = ENV['LIBUV_MAX_QUEUE'] || 50000
14
+ THREAD_POOL = ::Concurrent::ThreadPoolExecutor.new(
15
+ min_threads: LIBUV_MIN_POOL,
16
+ max_threads: LIBUV_MAX_POOL,
17
+ max_queue: LIBUV_MAX_QUEUE
18
+ )
19
+ CRITICAL = ::Mutex.new
20
+
21
+
22
+ module ClassMethods
23
+ # Get default reactor
24
+ #
25
+ # @return [::MTLibuv::Reactor]
26
+ def default
27
+ return @default unless @default.nil?
28
+ CRITICAL.synchronize {
29
+ return @default ||= create(::MTLibuv::Ext.default_loop)
30
+ }
31
+ end
32
+
33
+ # Create new MTLibuv reactor
34
+ #
35
+ # @return [::MTLibuv::Reactor]
36
+ def new(&blk)
37
+ memory = ::MTLibuv::Ext::LIBC.malloc(::MTLibuv::Ext.loop_size)
38
+ ::MTLibuv::Ext.loop_init(memory)
39
+
40
+ thread = create(memory)
41
+ if block_given?
42
+ ::Thread.new do
43
+ thread.run &blk
44
+ end
45
+ end
46
+ thread
47
+ end
48
+
49
+ # Build a Ruby MTLibuv reactor from an existing reactor pointer
50
+ #
51
+ # @return [::MTLibuv::Reactor]
52
+ def create(pointer)
53
+ allocate.tap { |i| i.send(:initialize, pointer) }
54
+ end
55
+
56
+ # Checks for the existence of a reactor on the current thread
57
+ #
58
+ # @return [::MTLibuv::Reactor | nil]
59
+ def current
60
+ Thread.current.thread_variable_get(:reactor)
61
+ end
62
+ end
63
+ extend ClassMethods
64
+
65
+
66
+ # Initialize a reactor using an FFI::Pointer to a libuv reactor
67
+ def initialize(pointer) # :notnew:
68
+ @pointer = pointer
69
+ @reactor = self
70
+ @run_count = 0
71
+ @ref_count = 0
72
+ @fiber_pool = FiberPool.new(self)
73
+
74
+ # Create an async call for scheduling work from other threads
75
+ @run_queue = Queue.new
76
+ @process_queue = @reactor.async { process_queue_cb }
77
+ @process_queue.unref
78
+
79
+ # Create a next tick timer
80
+ @next_tick = @reactor.timer { next_tick_cb }
81
+ @next_tick.unref
82
+
83
+ # Create an async call for ending the reactor
84
+ @stop_reactor = @reactor.async { stop_cb }
85
+ @stop_reactor.unref
86
+
87
+ # MTLibuv can prevent the application shutting down once the main thread has ended
88
+ # The addition of a prepare function prevents this from happening.
89
+ @reactor_prep = prepare {}
90
+ @reactor_prep.unref
91
+ @reactor_prep.start
92
+
93
+ # LibUV ingnores program interrupt by default.
94
+ # We provide normal behaviour and allow this to be overriden
95
+ @on_signal = []
96
+ sig_callback = proc { signal_cb }
97
+ self.signal(:INT, &sig_callback).unref
98
+ self.signal(:HUP, &sig_callback).unref
99
+ self.signal(:TERM, &sig_callback).unref
100
+
101
+ # Notify of errors
102
+ @throw_on_exit = nil
103
+ @reactor_notify_default = @reactor_notify = proc { |error|
104
+ @throw_on_exit = error
105
+ }
106
+ @fiber_pool.on_error &@reactor_notify
107
+ end
108
+
109
+ attr_reader :run_count, :fiber_pool
110
+
111
+
112
+ protected
113
+
114
+
115
+ def stop_cb
116
+ return unless @reactor_running
117
+ Thread.current.thread_variable_set(:reactor, nil)
118
+ @reactor_running = false
119
+
120
+ ::MTLibuv::Ext.stop(@pointer)
121
+ end
122
+
123
+ def signal_cb
124
+ if @on_signal.empty?
125
+ stop_cb
126
+ else
127
+ @on_signal.each(&:call)
128
+ end
129
+ end
130
+
131
+ def next_tick_cb
132
+ @next_tick_scheduled = false
133
+ @next_tick.unref
134
+ process_queue_cb
135
+ end
136
+
137
+ def process_queue_cb
138
+ # ensure we only execute what was required for this tick
139
+ length = @run_queue.length
140
+ update_time
141
+ length.times do
142
+ # This allows any item to pause its execution without effecting this loop
143
+ @fiber_pool.exec { process_item }
144
+ end
145
+ end
146
+
147
+ def process_item
148
+ begin
149
+ run = @run_queue.pop true # pop non-block
150
+ run.call
151
+ rescue Exception => e
152
+ @reactor.log e, 'performing next tick callback'
153
+ end
154
+ end
155
+
156
+
157
+ public
158
+
159
+
160
+ # Overwrite as errors in jRuby can literally hang VM when inspecting
161
+ # as many many classes will reference this class
162
+ def inspect
163
+ "#<#{self.class}:0x#{self.__id__.to_s(16)} NT=#{@run_queue.length}>"
164
+ end
165
+
166
+
167
+ def handle; @pointer; end
168
+
169
+ # Run the actual event reactor. This method will block until the reactor is stopped.
170
+ #
171
+ # @param run_type [:UV_RUN_DEFAULT, :UV_RUN_ONCE, :UV_RUN_NOWAIT]
172
+ # @yieldparam promise [::MTLibuv::Q::Promise] Yields a promise that can be used for logging unhandled
173
+ # exceptions on the reactor.
174
+ def run(run_type = :UV_RUN_DEFAULT)
175
+ if not @reactor_running
176
+ begin
177
+ @reactor_running = true
178
+ raise 'only one reactor allowed per-thread' if Thread.current.thread_variable_get(:reactor)
179
+
180
+ Thread.current.thread_variable_set(:reactor, @reactor)
181
+ @throw_on_exit = nil
182
+ update_time
183
+ @fiber_pool.reset
184
+ @fiber_pool.exec { yield @reactor } if block_given?
185
+ @run_count += 1
186
+ ::MTLibuv::Ext.run(@pointer, run_type) # This is blocking
187
+ ensure
188
+ Thread.current.thread_variable_set(:reactor, nil)
189
+ @reactor_running = false
190
+ @run_queue.clear
191
+ end
192
+
193
+ # Raise the last unhandled error to occur on the reactor thread
194
+ raise @throw_on_exit if @throw_on_exit
195
+
196
+ elsif block_given?
197
+ if reactor_thread?
198
+ update_time
199
+ yield @reactor
200
+ else
201
+ raise 'reactor already running on another thread'
202
+ end
203
+ end
204
+
205
+ @reactor
206
+ end
207
+
208
+ # Execute the provided block of code in a fiber from the pool
209
+ def exec
210
+ @fiber_pool.exec { yield }
211
+ end
212
+
213
+ # Prevents the reactor loop from stopping
214
+ def ref
215
+ if reactor_thread? && reactor_running?
216
+ @process_queue.ref if @ref_count == 0
217
+ @ref_count += 1
218
+ end
219
+ end
220
+
221
+ # Allows the reactor loop to stop
222
+ def unref
223
+ if reactor_thread? && reactor_running? && @ref_count > 0
224
+ @ref_count -= 1
225
+ @process_queue.unref if @ref_count == 0
226
+ end
227
+ end
228
+
229
+ # Return the number of active handles in the event loop
230
+ def active_handles
231
+ uvloop = Ext::UvLoop.new @pointer
232
+ uvloop[:active_handles]
233
+ end
234
+
235
+
236
+ # Provides a promise notifier for receiving un-handled exceptions
237
+ #
238
+ # @return [::MTLibuv::Q::Promise]
239
+ def notifier
240
+ @reactor_notify = if block_given?
241
+ Proc.new
242
+ else
243
+ @reactor_notify_default
244
+ end
245
+ self
246
+ end
247
+
248
+ # Creates a deferred result object for where the result of an operation may only be returned
249
+ # at some point in the future or is being processed on a different thread (thread safe)
250
+ #
251
+ # @return [::MTLibuv::Q::Deferred]
252
+ def defer
253
+ Q.defer(@reactor)
254
+ end
255
+
256
+ # Combines multiple promises into a single promise that is resolved when all of the input
257
+ # promises are resolved. (thread safe)
258
+ #
259
+ # @param *promises [::MTLibuv::Q::Promise] a number of promises that will be combined into a single promise
260
+ # @return [::MTLibuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
261
+ # each value corresponding to the promise at the same index in the `promises` array. If any of
262
+ # the promises is resolved with a rejection, this resulting promise will be resolved with the
263
+ # same rejection.
264
+ def all(*promises)
265
+ Q.all(@reactor, *promises)
266
+ end
267
+
268
+ #
269
+ # Combines multiple promises into a single promise that is resolved when any of the input
270
+ # promises are resolved.
271
+ #
272
+ # @param *promises [::MTLibuv::Q::Promise] a number of promises that will be combined into a single promise
273
+ # @return [::MTLibuv::Q::Promise] Returns a single promise
274
+ def any(*promises)
275
+ Q.any(@reactor, *promises)
276
+ end
277
+
278
+ #
279
+ # Combines multiple promises into a single promise that is resolved when all of the input
280
+ # promises are resolved or rejected.
281
+ #
282
+ # @param *promises [::MTLibuv::Q::Promise] a number of promises that will be combined into a single promise
283
+ # @return [::MTLibuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
284
+ # each [result, wasResolved] value pair corresponding to a at the same index in the `promises` array.
285
+ def finally(*promises)
286
+ Q.finally(@reactor, *promises)
287
+ end
288
+
289
+ # Creates a promise that is resolved as rejected with the specified reason. This api should be
290
+ # used to forward rejection in a chain of promises. If you are dealing with the last promise in
291
+ # a promise chain, you don't need to worry about it.
292
+ def reject(reason)
293
+ Q.reject(@reactor, reason)
294
+ end
295
+
296
+ # forces reactor time update, useful for getting more granular times
297
+ #
298
+ # @return nil
299
+ def update_time
300
+ ::MTLibuv::Ext.update_time(@pointer)
301
+ self
302
+ end
303
+
304
+ # Get current time in milliseconds
305
+ #
306
+ # @return [Integer]
307
+ def now
308
+ ::MTLibuv::Ext.now(@pointer)
309
+ end
310
+
311
+ # Lookup an error code and return is as an error object
312
+ #
313
+ # @param err [Integer] The error code to look up.
314
+ # @return [::MTLibuv::Error]
315
+ def lookup_error(err)
316
+ name = ::MTLibuv::Ext.err_name(err)
317
+
318
+ if name
319
+ msg = ::MTLibuv::Ext.strerror(err)
320
+ ::MTLibuv::Error.const_get(name.to_sym).new("#{msg}, #{name}:#{err}")
321
+ else
322
+ # We want a back-trace in this case
323
+ raise "error lookup failed for code #{err}"
324
+ end
325
+ rescue Exception => e
326
+ @reactor.log e, 'performing error lookup'
327
+ e
328
+ end
329
+
330
+ def sleep(msecs)
331
+ fiber = Fiber.current
332
+ time = timer {
333
+ time.close
334
+ fiber.resume
335
+ }.start(msecs)
336
+ Fiber.yield
337
+ end
338
+
339
+ # Get a new TCP instance
340
+ #
341
+ # @return [::MTLibuv::TCP]
342
+ def tcp(**opts, &callback)
343
+ TCP.new(@reactor, progress: callback, **opts)
344
+ end
345
+
346
+ # Get a new UDP instance
347
+ #
348
+ # @return [::MTLibuv::UDP]
349
+ def udp(**opts, &callback)
350
+ UDP.new(@reactor, progress: callback, **opts)
351
+ end
352
+
353
+ # Get a new TTY instance
354
+ #
355
+ # @param fileno [Integer] Integer file descriptor of a tty device
356
+ # @param readable [true, false] Boolean indicating if TTY is readable
357
+ # @return [::MTLibuv::TTY]
358
+ def tty(fileno, readable = false)
359
+ assert_type(Integer, fileno, "io#fileno must return an integer file descriptor, #{fileno.inspect} given")
360
+
361
+ TTY.new(@reactor, fileno, readable)
362
+ end
363
+
364
+ # Get a new Pipe instance
365
+ #
366
+ # @param ipc [true, false] indicate if a handle will be used for ipc, useful for sharing tcp socket between processes
367
+ # @return [::MTLibuv::Pipe]
368
+ def pipe(ipc = false)
369
+ Pipe.new(@reactor, ipc)
370
+ end
371
+
372
+ # Get a new timer instance
373
+ #
374
+ # @param callback [Proc] the callback to be called on timer trigger
375
+ # @return [::MTLibuv::Timer]
376
+ def timer
377
+ handle = Timer.new(@reactor)
378
+ handle.progress &Proc.new if block_given?
379
+ handle
380
+ end
381
+
382
+ # Get a new Prepare handle
383
+ #
384
+ # @return [::MTLibuv::Prepare]
385
+ def prepare
386
+ handle = Prepare.new(@reactor)
387
+ handle.progress &Proc.new if block_given?
388
+ handle
389
+ end
390
+
391
+ # Get a new Check handle
392
+ #
393
+ # @return [::MTLibuv::Check]
394
+ def check
395
+ handle = Check.new(@reactor)
396
+ handle.progress &Proc.new if block_given?
397
+ handle
398
+ end
399
+
400
+ # Get a new Idle handle
401
+ #
402
+ # @param callback [Proc] the callback to be called on idle trigger
403
+ # @return [::MTLibuv::Idle]
404
+ def idle
405
+ handle = Idle.new(@reactor)
406
+ handle.progress &Proc.new if block_given?
407
+ handle
408
+ end
409
+
410
+ # Get a new Async handle
411
+ #
412
+ # @return [::MTLibuv::Async]
413
+ def async
414
+ handle = Async.new(@reactor)
415
+ handle.progress &Proc.new if block_given?
416
+ handle
417
+ end
418
+
419
+ # Get a new signal handler
420
+ #
421
+ # @return [::MTLibuv::Signal]
422
+ def signal(signum = nil)
423
+ handle = Signal.new(@reactor)
424
+ handle.progress &Proc.new if block_given?
425
+ handle.start(signum) if signum
426
+ handle
427
+ end
428
+
429
+ # Allows user defined behaviour when sig int is received
430
+ def on_program_interrupt(&callback)
431
+ @on_signal << callback
432
+ self
433
+ end
434
+
435
+ # Queue some work for processing in the libuv thread pool
436
+ #
437
+ # @param callback [Proc] the callback to be called in the thread pool
438
+ # @return [::MTLibuv::Work]
439
+ # @raise [ArgumentError] if block is not given
440
+ def work
441
+ ref
442
+ d = defer
443
+ THREAD_POOL.post do
444
+ begin
445
+ d.resolve(yield)
446
+ rescue Exception => e
447
+ d.reject(e)
448
+ end
449
+ end
450
+ promise = d.promise
451
+ promise.finally { unref }
452
+ promise
453
+ end
454
+
455
+ # Lookup a hostname
456
+ #
457
+ # @param hostname [String] the domain name to lookup
458
+ # @param port [Integer, String] the service being connected too
459
+ # @param callback [Proc] the callback to be called on success
460
+ # @return [::MTLibuv::Dns]
461
+ def lookup(hostname, hint = :IPv4, port = 9, wait: true)
462
+ dns = Dns.new(@reactor, hostname, port, hint, wait: wait) # Work is a promise object
463
+ if wait
464
+ dns.results
465
+ else
466
+ dns.then &Proc.new if block_given?
467
+ dns
468
+ end
469
+ end
470
+
471
+ # Get a new FSEvent instance
472
+ #
473
+ # @param path [String] the path to the file or folder for watching
474
+ # @return [::MTLibuv::FSEvent]
475
+ # @raise [ArgumentError] if path is not a string
476
+ def fs_event(path)
477
+ assert_type(String, path)
478
+ FSEvent.new(@reactor, path)
479
+ end
480
+
481
+ # Opens a file and returns an object that can be used to manipulate it
482
+ #
483
+ # @param path [String] the path to the file or folder for watching
484
+ # @param flags [Integer] see ruby File::Constants
485
+ # @param mode [Integer]
486
+ # @return [::MTLibuv::File]
487
+ def file(path, flags = 0, mode: 0, **opts, &blk)
488
+ assert_type(String, path, "path must be a String")
489
+ assert_type(Integer, flags, "flags must be an Integer")
490
+ assert_type(Integer, mode, "mode must be an Integer")
491
+ File.new(@reactor, path, flags, mode: mode, **opts, &blk)
492
+ end
493
+
494
+ # Returns an object for manipulating the filesystem
495
+ #
496
+ # @return [::MTLibuv::Filesystem]
497
+ def filesystem
498
+ Filesystem.new(@reactor)
499
+ end
500
+
501
+ def spawn(cmd, **args)
502
+ Spawn.new(@reactor, cmd, **args)
503
+ end
504
+
505
+ # Schedule some work to be processed on the event reactor as soon as possible (thread safe)
506
+ #
507
+ # @yield the callback to be called on the reactor thread
508
+ def schedule
509
+ if reactor_thread?
510
+ yield
511
+ else
512
+ @run_queue << Proc.new
513
+ @process_queue.call
514
+ end
515
+ self
516
+ end
517
+
518
+ # Queue some work to be processed in the next iteration of the event reactor (thread safe)
519
+ #
520
+ # @param callback [Proc] the callback to be called on the reactor thread
521
+ def next_tick(&block)
522
+ @run_queue << block
523
+ if reactor_thread?
524
+ # Create a next tick timer
525
+ if not @next_tick_scheduled
526
+ @next_tick.start(0)
527
+ @next_tick_scheduled = true
528
+ @next_tick.ref
529
+ end
530
+ else
531
+ @process_queue.call
532
+ end
533
+
534
+ self
535
+ end
536
+
537
+ # Notifies the reactor there was an event that should be logged
538
+ #
539
+ # @param error [Exception] the error
540
+ # @param msg [String|nil] optional context on the error
541
+ # @param trace [Array<String>] optional additional trace of caller if async
542
+ def log(error, msg = nil, trace = nil)
543
+ @reactor_notify.call(error, msg, trace)
544
+ end
545
+
546
+ # Closes handles opened by the reactor class and completes the current reactor iteration (thread safe)
547
+ def stop
548
+ return unless @reactor_running
549
+ @stop_reactor.call
550
+ end
551
+
552
+ # True if the calling thread is the same thread as the reactor.
553
+ #
554
+ # @return [Boolean]
555
+ def reactor_thread?
556
+ self == Thread.current.thread_variable_get(:reactor)
557
+ end
558
+
559
+ # Tells you whether the MTLibuv reactor reactor is currently running.
560
+ #
561
+ # @return [Boolean]
562
+ def reactor_running?
563
+ @reactor_running
564
+ end
565
+ alias_method :running?, :reactor_running?
566
+ end
567
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTLibuv
4
+ class Signal < Handle
5
+
6
+
7
+ define_callback function: :on_sig, params: [:pointer, :int]
8
+
9
+
10
+ SIGNALS = {
11
+ :HUP => 1,
12
+ :SIGHUP => 1,
13
+ :INT => 2,
14
+ :SIGINT => 2,
15
+ :TERM => 15,
16
+ :SIGTERM => 15,
17
+ :BREAK => 21,
18
+ :SIGBREAK => 21,
19
+ :WINCH => 28,
20
+ :SIGWINCH => 28
21
+ }
22
+
23
+
24
+ # @param reactor [::MTLibuv::Reactor] reactor this signal handler will be associated
25
+ # @param callback [Proc] callback to be called when the signal is triggered
26
+ def initialize(reactor)
27
+ @reactor = reactor
28
+
29
+ signal_ptr = ::MTLibuv::Ext.allocate_handle_signal
30
+ error = check_result(::MTLibuv::Ext.signal_init(reactor.handle, signal_ptr))
31
+
32
+ super(signal_ptr, error)
33
+ end
34
+
35
+ # Enables the signal handler.
36
+ def start(signal)
37
+ return if @closed
38
+ signal = SIGNALS[signal] if signal.is_a? Symbol
39
+ error = check_result ::MTLibuv::Ext.signal_start(handle, callback(:on_sig), signal)
40
+ reject(error) if error
41
+ self
42
+ end
43
+
44
+ # Disables the signal handler.
45
+ def stop
46
+ return if @closed
47
+ error = check_result ::MTLibuv::Ext.signal_stop(handle)
48
+ reject(error) if error
49
+ self
50
+ end
51
+
52
+
53
+ private
54
+
55
+
56
+ def on_sig(handle, signal)
57
+ @reactor.exec do
58
+ defer.notify(signal) # notify of a call
59
+ end
60
+ end
61
+ end
62
+ end