mt-libuv 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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