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,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTLibuv
4
+ class Spawn < Handle
5
+ define_callback function: :on_exit, params: [:pointer, :int64, :int]
6
+
7
+ attr_reader :stdin, :stdout, :stderr
8
+
9
+ # @param reactor [::MTLibuv::Reactor] reactor this timer will be associated
10
+ # @param callback [Proc] callback to be called when the timer is triggered
11
+ def initialize(reactor, cmd, working_dir: '.', args: [], env: nil, flags: 0, mode: :capture)
12
+ @reactor = reactor
13
+
14
+ process_ptr = ::MTLibuv::Ext.allocate_handle_process
15
+ @options = Ext::UvProcessOptions.new
16
+
17
+ # Configure IO objects
18
+ @io_obj = Ext::StdioObjs.new
19
+ case mode.to_sym
20
+ when :capture
21
+ @stdin = @reactor.pipe
22
+ @stdout = @reactor.pipe
23
+ @stderr = @reactor.pipe
24
+ @io_obj[:stdin] = build_stdio(:CREATE_READABLE_PIPE, pipe: @stdin)
25
+ @io_obj[:stdout] = build_stdio(:CREATE_WRITABLE_PIPE, pipe: @stdout)
26
+ @io_obj[:stderr] = build_stdio(:CREATE_WRITABLE_PIPE, pipe: @stderr)
27
+ when :ignore
28
+ @io_obj[:stdin] = build_stdio(:UV_IGNORE)
29
+ @io_obj[:stdout] = build_stdio(:UV_IGNORE)
30
+ @io_obj[:stderr] = build_stdio(:UV_IGNORE)
31
+ when :inherit
32
+ @io_obj[:stdin] = build_stdio(:UV_INHERIT_FD, fd: ::STDIN.fileno)
33
+ @io_obj[:stdout] = build_stdio(:UV_INHERIT_FD, fd: ::STDOUT.fileno)
34
+ @io_obj[:stderr] = build_stdio(:UV_INHERIT_FD, fd: ::STDERR.fileno)
35
+ end
36
+
37
+ # Configure arguments
38
+ @cmd = FFI::MemoryPointer.from_string(cmd)
39
+ @args = Array(args).map { |arg| FFI::MemoryPointer.from_string(arg) }
40
+ @args.unshift(@cmd)
41
+ @args_ptr = FFI::MemoryPointer.new(:pointer, @args.length + 1)
42
+ @args_ptr.write_array_of_pointer(@args)
43
+
44
+ # Configure environment
45
+ if env
46
+ @env = Array(env).map { |e| FFI::MemoryPointer.from_string(e) }
47
+ @env_ptr = FFI::MemoryPointer.new(:pointer, @env.length + 1)
48
+ @env_ptr.write_array_of_pointer(@env)
49
+ end
50
+
51
+ @working_dir = FFI::MemoryPointer.from_string(working_dir)
52
+
53
+ # Apply the options
54
+ @options[:exit_cb] = callback(:on_exit, process_ptr.address)
55
+ @options[:file] = @cmd
56
+ @options[:args] = @args_ptr
57
+ @options[:env] = @env_ptr
58
+ @options[:cwd] = @working_dir
59
+ @options[:flags] = 0
60
+ @options[:stdio_count] = 3
61
+ @options[:stdio] = @io_obj
62
+
63
+ error = check_result(::MTLibuv::Ext.spawn(reactor.handle, process_ptr, @options))
64
+ super(process_ptr, error)
65
+ end
66
+
67
+ def kill(signal = 2)
68
+ return self if @closed
69
+ ::MTLibuv::Ext.process_kill(handle, signal)
70
+ self
71
+ end
72
+
73
+
74
+ private
75
+
76
+
77
+ def build_stdio(flags, pipe: nil, fd: nil)
78
+ io = Ext::UvStdioContainer.new
79
+ io[:flags] = flags
80
+ if pipe
81
+ io[:data][:pipe_handle] = pipe.handle
82
+ elsif fd
83
+ io[:data][:fd] = fd
84
+ end
85
+ io
86
+ end
87
+
88
+ def on_exit(handle, exit_status, term_signal)
89
+ @reactor.exec do
90
+ if exit_status == 0
91
+ @defer.resolve(term_signal)
92
+ else
93
+ err = ::MTLibuv::Error::ProcessExitCode.new "Non-zero exit code returned #{exit_status}"
94
+ err.exit_status = exit_status
95
+ err.term_signal = term_signal
96
+ @defer.reject(err)
97
+ end
98
+
99
+ if @stdin
100
+ @reactor.next_tick do
101
+ @stdin.close
102
+ @stdout.close
103
+ @stderr.close
104
+
105
+ close
106
+ end
107
+ else
108
+ close
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,465 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipaddr'
4
+ require 'mt-ruby-tls'
5
+
6
+
7
+ module MTLibuv
8
+ class TCP < Handle
9
+ include Stream, Net
10
+
11
+
12
+ define_callback function: :on_connect, params: [:pointer, :int]
13
+
14
+
15
+ TLS_ERROR = "TLS write failed"
16
+
17
+
18
+ attr_reader :connected
19
+ attr_reader :protocol
20
+ attr_reader :tls
21
+
22
+ # Check if tls active on the socket
23
+ def tls?; !@tls.nil?; end
24
+
25
+
26
+ def initialize(reactor, acceptor = nil, progress: nil, flags: nil, **tls_options)
27
+ @reactor = reactor
28
+ @progress = progress
29
+ @tls_options = tls_options
30
+
31
+ tcp_ptr = ::MTLibuv::Ext.allocate_handle_tcp
32
+ error = if flags
33
+ check_result(::MTLibuv::Ext.tcp_init_ex(reactor.handle, tcp_ptr, flags))
34
+ else
35
+ check_result(::MTLibuv::Ext.tcp_init(reactor.handle, tcp_ptr))
36
+ end
37
+
38
+ if acceptor && error.nil?
39
+ error = check_result(::MTLibuv::Ext.accept(acceptor, tcp_ptr))
40
+ @connected = true
41
+ else
42
+ @connected = false
43
+ end
44
+
45
+ super(tcp_ptr, error)
46
+ end
47
+
48
+
49
+ #
50
+ # TLS Abstraction ----------------------
51
+ # --------------------------------------
52
+ #
53
+ def start_tls(args = {})
54
+ return self unless @connected && @tls.nil?
55
+
56
+ args[:verify_peer] = true if @on_verify
57
+
58
+ @handshake = false
59
+ @pending_writes = []
60
+ @tls_options.merge!(args)
61
+
62
+ hosts = @tls_options[:hosts]
63
+ if hosts && hosts[0]
64
+ opts = @tls_options.merge(hosts[0])
65
+ @tls = ::RubyTls::SSL::Box.new(opts[:server], self, opts)
66
+ hosts[1..-1].each do |host_opts|
67
+ @tls.add_host(**host_opts)
68
+ end
69
+ else
70
+ @tls = ::RubyTls::SSL::Box.new(@tls_options[:server], self, @tls_options)
71
+ end
72
+ @tls.start
73
+ self
74
+ end
75
+
76
+ # Push through any pending writes when handshake has completed
77
+ def handshake_cb(protocol = nil)
78
+ @handshake = true
79
+ @protocol = protocol
80
+
81
+ writes = @pending_writes
82
+ @pending_writes = nil
83
+ writes.each do |deferred, data|
84
+ @pending_write = deferred
85
+ @tls.encrypt(data)
86
+ end
87
+
88
+ begin
89
+ @on_handshake.call(self, protocol) if @on_handshake
90
+ rescue => e
91
+ @reactor.log e, 'performing TLS handshake callback'
92
+ end
93
+ end
94
+
95
+ # Provide a callback once the TLS handshake has completed
96
+ def on_handshake(&blk)
97
+ @on_handshake = blk
98
+ self
99
+ end
100
+
101
+ # This is clear text data that has been decrypted
102
+ # Same as stream.rb on_read for clear text
103
+ def dispatch_cb(data)
104
+ begin
105
+ @progress.call data, self
106
+ rescue Exception => e
107
+ @reactor.log e, 'performing TLS read data callback'
108
+ end
109
+ end
110
+
111
+ # We resolve the existing tls write promise with a the
112
+ # real writes promise (a close may have occurred)
113
+ def transmit_cb(data)
114
+ if @pending_write
115
+ @pending_write.resolve(direct_write(data))
116
+ @pending_write = nil
117
+ else
118
+ direct_write(data)
119
+ end
120
+ end
121
+
122
+ # Close can be called multiple times
123
+ def close_cb
124
+ if @pending_write
125
+ @pending_write.reject(TLS_ERROR)
126
+ @pending_write = nil
127
+ end
128
+
129
+ # Shutdown the stream
130
+ close
131
+ end
132
+
133
+ def verify_cb(cert)
134
+ if @on_verify
135
+ begin
136
+ return @on_verify.call cert
137
+ rescue => e
138
+ @reactor.log e, 'performing TLS verify callback'
139
+ return false
140
+ end
141
+ end
142
+
143
+ true
144
+ end
145
+
146
+ # overwrite the default close to ensure
147
+ # pending writes are rejected
148
+ def close
149
+ return self if @closed
150
+
151
+ # Free tls memory
152
+ # Next tick as may recieve data after closing
153
+ if @tls
154
+ @reactor.next_tick do
155
+ @tls.cleanup
156
+ end
157
+ end
158
+ @connected = false
159
+
160
+ if @pending_writes
161
+ @pending_writes.each do |deferred, data|
162
+ deferred.reject(TLS_ERROR)
163
+ end
164
+ @pending_writes = nil
165
+ end
166
+
167
+ super
168
+ end
169
+
170
+ # Verify peers will be called for each cert in the chain
171
+ def verify_peer(&blk)
172
+ @on_verify = blk
173
+ self
174
+ end
175
+
176
+ alias_method :direct_write, :write
177
+ def write(data, wait: false)
178
+ if @tls
179
+ deferred = @reactor.defer
180
+
181
+ if @handshake
182
+ @pending_write = deferred
183
+ @tls.encrypt(data)
184
+ else
185
+ @pending_writes << [deferred, data]
186
+ end
187
+
188
+ if wait
189
+ return deferred.promise if wait == :promise
190
+ deferred.promise.value
191
+ end
192
+
193
+ self
194
+ else
195
+ direct_write(data, wait: wait)
196
+ end
197
+ end
198
+
199
+ alias_method :do_shutdown, :shutdown
200
+ def shutdown
201
+ if @pending_writes && @pending_writes.length > 0
202
+ @pending_writes[-1][0].promise.finally { do_shutdown }
203
+ else
204
+ do_shutdown
205
+ end
206
+ self
207
+ end
208
+
209
+ def add_host(**host_opts)
210
+ @tls_options[:hosts] ||= []
211
+ @tls_options[:hosts] << host_opts
212
+ end
213
+
214
+ def remove_host(name)
215
+ if @tls_options[:hosts]
216
+ found = nil
217
+ @tls_options[:hosts].each do |host|
218
+ if host[:host_name] == name
219
+ found = host
220
+ break
221
+ end
222
+ end
223
+ @tls_options[:hosts].delete(found) if found
224
+ end
225
+ end
226
+ #
227
+ # END TLS Abstraction ------------------
228
+ # --------------------------------------
229
+ #
230
+
231
+ def bind(ip, port, **tls_options, &blk)
232
+ return self if @closed
233
+
234
+ @on_accept = blk
235
+ @on_listen = proc { accept }
236
+
237
+ assert_type(String, ip, IP_ARGUMENT_ERROR)
238
+ assert_type(Integer, port, PORT_ARGUMENT_ERROR)
239
+
240
+ begin
241
+ @tcp_socket = create_socket(IPAddr.new(ip), port)
242
+ @tcp_socket.bind
243
+ @tls_options.merge!(tls_options)
244
+ @tls_options[:server] = true
245
+ rescue Exception => e
246
+ reject(e)
247
+ end
248
+
249
+ self
250
+ end
251
+
252
+ def open(fd, binding = true)
253
+ return self if @closed
254
+
255
+ if binding
256
+ @on_listen = proc { accept }
257
+ @on_accept = Proc.new
258
+ elsif block_given?
259
+ @callback = Proc.new
260
+ else
261
+ @coroutine = @reactor.defer
262
+ end
263
+ error = check_result ::MTLibuv::Ext.tcp_open(handle, fd)
264
+ reject(error) if error
265
+ @coroutine.promise.value if @coroutine
266
+
267
+ self
268
+ end
269
+
270
+ def connect(ip, port)
271
+ return self if @closed
272
+
273
+ assert_type(String, ip, IP_ARGUMENT_ERROR)
274
+ assert_type(Integer, port, PORT_ARGUMENT_ERROR)
275
+
276
+ begin
277
+ @tcp_socket = create_socket(IPAddr.new(ip), port)
278
+ @tcp_socket.connect(callback(:on_connect, @tcp_socket.connect_req.address))
279
+ rescue Exception => e
280
+ reject(e)
281
+ end
282
+
283
+ if block_given?
284
+ @callback = Proc.new
285
+ else
286
+ @coroutine = @reactor.defer
287
+ @coroutine.promise.value
288
+ end
289
+
290
+ self
291
+ end
292
+
293
+ # The name of the client (local) end of the socket
294
+ def sockname
295
+ return [] if @closed
296
+ sockaddr, len = get_sockaddr_and_len
297
+ check_result! ::MTLibuv::Ext.tcp_getsockname(handle, sockaddr, len)
298
+ get_ip_and_port(::MTLibuv::Ext::Sockaddr.new(sockaddr), len.get_int(0))
299
+ end
300
+
301
+ # The IP address of the peer (remote) end of the socket
302
+ def peername
303
+ return [] if @closed
304
+ sockaddr, len = get_sockaddr_and_len
305
+ check_result! ::MTLibuv::Ext.tcp_getpeername(handle, sockaddr, len)
306
+ get_ip_and_port(::MTLibuv::Ext::Sockaddr.new(sockaddr), len.get_int(0))
307
+ end
308
+
309
+ def enable_nodelay
310
+ return self if @closed
311
+ check_result ::MTLibuv::Ext.tcp_nodelay(handle, 1)
312
+ self
313
+ end
314
+
315
+ def disable_nodelay
316
+ return self if @closed
317
+ check_result ::MTLibuv::Ext.tcp_nodelay(handle, 0)
318
+ self
319
+ end
320
+
321
+ def enable_keepalive(delay)
322
+ return self if @closed # The to_i asserts integer
323
+ check_result ::MTLibuv::Ext.tcp_keepalive(handle, 1, delay.to_i)
324
+ self
325
+ end
326
+
327
+ def disable_keepalive
328
+ return self if @closed
329
+ check_result ::MTLibuv::Ext.tcp_keepalive(handle, 0, 0)
330
+ self
331
+ end
332
+
333
+ def enable_simultaneous_accepts
334
+ return self if @closed
335
+ check_result ::MTLibuv::Ext.tcp_simultaneous_accepts(handle, 1)
336
+ self
337
+ end
338
+
339
+ def disable_simultaneous_accepts
340
+ return self if @closed
341
+ check_result ::MTLibuv::Ext.tcp_simultaneous_accepts(handle, 0)
342
+ self
343
+ end
344
+
345
+
346
+ private
347
+
348
+
349
+ def create_socket(ip, port)
350
+ if ip.ipv4?
351
+ Socket4.new(reactor, handle, ip.to_s, port)
352
+ else
353
+ Socket6.new(reactor, handle, ip.to_s, port)
354
+ end
355
+ end
356
+
357
+ def on_connect(req, status)
358
+ cleanup_callbacks req.address
359
+ ::MTLibuv::Ext.free(req)
360
+ e = check_result(status)
361
+
362
+ @reactor.exec do
363
+ if e
364
+ reject(e)
365
+ else
366
+ @connected = true
367
+
368
+ begin
369
+ if @callback
370
+ @callback.call(self)
371
+ @callback = nil
372
+ elsif @coroutine
373
+ @coroutine.resolve(nil)
374
+ @coroutine = nil
375
+ else
376
+ raise ArgumentError, 'no callback provided'
377
+ end
378
+ rescue Exception => e
379
+ @reactor.log e, 'performing TCP connection callback'
380
+ end
381
+ end
382
+ end
383
+ end
384
+
385
+ def accept
386
+ begin
387
+ raise RuntimeError, CLOSED_HANDLE_ERROR if @closed
388
+ tcp = TCP.new(reactor, handle, **@tls_options)
389
+
390
+ @reactor.exec do
391
+ begin
392
+ @on_accept.call(tcp)
393
+ rescue Exception => e
394
+ @reactor.log e, 'performing TCP accept callback'
395
+ end
396
+ end
397
+ rescue Exception => e
398
+ @reactor.log e, 'failed to accept TCP connection'
399
+ end
400
+ end
401
+
402
+
403
+ class SocketBase
404
+ include Resource
405
+
406
+ def initialize(reactor, tcp, ip, port)
407
+ @ip = ip
408
+ @port = port
409
+ @tcp = tcp
410
+ @reactor = reactor
411
+ @req = ::MTLibuv::Ext.allocate_request_connect
412
+ end
413
+
414
+ def bind
415
+ check_result! ::MTLibuv::Ext.tcp_bind(@tcp, ip_addr, 0)
416
+ end
417
+
418
+ def connect(callback)
419
+ @callback = callback
420
+ check_result!(tcp_connect)
421
+ end
422
+
423
+ def connect_req
424
+ @req
425
+ end
426
+
427
+
428
+ protected
429
+
430
+
431
+ def tcp_connect
432
+ ::MTLibuv::Ext.tcp_connect(
433
+ @req,
434
+ @tcp,
435
+ ip_addr,
436
+ @callback
437
+ )
438
+ end
439
+ end
440
+
441
+
442
+ class Socket4 < SocketBase
443
+ protected
444
+
445
+
446
+ def ip_addr
447
+ @sockaddr = Ext::SockaddrIn.new
448
+ check_result! ::MTLibuv::Ext.ip4_addr(@ip, @port, @sockaddr)
449
+ @sockaddr
450
+ end
451
+ end
452
+
453
+
454
+ class Socket6 < SocketBase
455
+ protected
456
+
457
+
458
+ def ip_addr
459
+ @sockaddr = Ext::SockaddrIn6.new
460
+ check_result! ::MTLibuv::Ext.ip6_addr(@ip, @port, @sockaddr)
461
+ @sockaddr
462
+ end
463
+ end
464
+ end
465
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTLibuv
4
+ class Timer < Handle
5
+
6
+
7
+ define_callback function: :on_timer
8
+
9
+
10
+ # @param reactor [::MTLibuv::Reactor] reactor this timer will be associated
11
+ # @param callback [Proc] callback to be called when the timer is triggered
12
+ def initialize(reactor)
13
+ @reactor = reactor
14
+
15
+ timer_ptr = ::MTLibuv::Ext.allocate_handle_timer
16
+ error = check_result(::MTLibuv::Ext.timer_init(reactor.handle, timer_ptr))
17
+ @stopped = true
18
+
19
+ super(timer_ptr, error)
20
+ end
21
+
22
+ # Enables the timer. A repeat of 0 means no repeat
23
+ #
24
+ # @param timeout [Integer] time in milliseconds before the timer callback is triggered the first time
25
+ # @param repeat [Integer] time in milliseconds between repeated callbacks after the first
26
+ def start(timeout, repeat = 0)
27
+ return if @closed
28
+ @stopped = false
29
+
30
+ # prevent timeouts less than 0 (very long time otherwise as cast to an unsigned)
31
+ # and you probably don't want to wait a few lifetimes
32
+ timeout = timeout.to_i
33
+ timeout = 0 if timeout < 0
34
+
35
+ error = check_result ::MTLibuv::Ext.timer_start(handle, callback(:on_timer), timeout, repeat.to_i)
36
+ reject(error) if error
37
+
38
+ self
39
+ end
40
+
41
+ # Disables the timer.
42
+ def stop
43
+ return if @stopped || @closed
44
+ @stopped = true
45
+ error = check_result ::MTLibuv::Ext.timer_stop(handle)
46
+ reject(error) if error
47
+
48
+ self
49
+ end
50
+
51
+ # Resets the current repeat
52
+ def again
53
+ return if @closed
54
+ error = check_result ::MTLibuv::Ext.timer_again(handle)
55
+ reject(error) if error
56
+
57
+ self
58
+ end
59
+
60
+ # Set the current repeat timeout
61
+ # Repeat is the time in milliseconds between repeated callbacks after the initial timeout fires
62
+ #
63
+ # @param time [Integer] time in milliseconds between repeated callbacks after the first
64
+ def repeat=(time)
65
+ return if @closed
66
+ error = check_result ::MTLibuv::Ext.timer_set_repeat(handle, time.to_i)
67
+ reject(error) if error
68
+ time
69
+ end
70
+
71
+ # Set or gets the current repeat timeout
72
+ # Repeat is the time in milliseconds between repeated callbacks after the initial timeout fires
73
+ #
74
+ # @param times [Integer] time in milliseconds between repeated callbacks after the first
75
+ def repeat(time = nil)
76
+ return if @closed
77
+ if time.nil?
78
+ ::MTLibuv::Ext.timer_get_repeat(handle)
79
+ else
80
+ self.repeat = time
81
+ self
82
+ end
83
+ end
84
+
85
+ # Used to update the callback to be triggered by the timer
86
+ #
87
+ # @param callback [Proc] the callback to be called by the timer
88
+ def progress(&callback)
89
+ @callback = callback
90
+ self
91
+ end
92
+
93
+
94
+ private
95
+
96
+
97
+ def on_timer(handle)
98
+ @reactor.exec do
99
+ begin
100
+ @callback.call
101
+ rescue Exception => e
102
+ @reactor.log e, 'performing timer callback'
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end