libuv 2.0.12 → 3.0.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/README.md +67 -34
  4. data/lib/libuv.rb +30 -5
  5. data/lib/libuv/async.rb +16 -10
  6. data/lib/libuv/check.rb +19 -12
  7. data/lib/libuv/coroutines.rb +39 -12
  8. data/lib/libuv/dns.rb +25 -18
  9. data/lib/libuv/error.rb +2 -0
  10. data/lib/libuv/ext/ext.rb +28 -36
  11. data/lib/libuv/ext/platform/darwin_x64.rb +2 -0
  12. data/lib/libuv/ext/platform/unix.rb +2 -0
  13. data/lib/libuv/ext/platform/windows.rb +2 -0
  14. data/lib/libuv/ext/tasks.rb +2 -0
  15. data/lib/libuv/ext/tasks/mac.rb +2 -0
  16. data/lib/libuv/ext/tasks/unix.rb +2 -0
  17. data/lib/libuv/ext/tasks/win.rb +2 -0
  18. data/lib/libuv/ext/types.rb +2 -1
  19. data/lib/libuv/file.rb +67 -50
  20. data/lib/libuv/filesystem.rb +63 -61
  21. data/lib/libuv/fs_event.rb +7 -4
  22. data/lib/libuv/handle.rb +30 -14
  23. data/lib/libuv/idle.rb +17 -10
  24. data/lib/libuv/mixins/accessors.rb +41 -0
  25. data/lib/libuv/mixins/assertions.rb +3 -1
  26. data/lib/libuv/mixins/fs_checks.rb +29 -6
  27. data/lib/libuv/mixins/listener.rb +4 -2
  28. data/lib/libuv/mixins/net.rb +4 -2
  29. data/lib/libuv/mixins/resource.rb +5 -3
  30. data/lib/libuv/mixins/stream.rb +128 -35
  31. data/lib/libuv/pipe.rb +54 -27
  32. data/lib/libuv/prepare.rb +19 -12
  33. data/lib/libuv/q.rb +109 -101
  34. data/lib/libuv/{loop.rb → reactor.rb} +163 -85
  35. data/lib/libuv/signal.rb +13 -5
  36. data/lib/libuv/tcp.rb +109 -63
  37. data/lib/libuv/timer.rb +44 -24
  38. data/lib/libuv/tty.rb +8 -3
  39. data/lib/libuv/udp.rb +49 -22
  40. data/lib/libuv/version.rb +3 -1
  41. data/lib/libuv/work.rb +14 -10
  42. data/libuv.gemspec +11 -9
  43. data/spec/async_spec.rb +13 -13
  44. data/spec/coroutines_spec.rb +20 -50
  45. data/spec/defer_spec.rb +182 -311
  46. data/spec/dns_spec.rb +51 -41
  47. data/spec/dsl_spec.rb +43 -0
  48. data/spec/filesystem_spec.rb +65 -87
  49. data/spec/idle_spec.rb +19 -33
  50. data/spec/pipe_spec.rb +25 -32
  51. data/spec/tcp_spec.rb +116 -53
  52. data/spec/timer_spec.rb +3 -3
  53. data/spec/udp_spec.rb +16 -17
  54. data/spec/zen_spec.rb +2 -3
  55. metadata +37 -30
@@ -1,19 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
4
 
3
5
  module Libuv
4
- class Loop
6
+ class Reactor
5
7
  include Resource, Assertions
8
+ extend Accessors
6
9
 
7
10
 
8
- LOOPS = ThreadSafe::Cache.new
9
- CRITICAL = Mutex.new
10
- @@use_fibers = false
11
+ REACTORS = ::Concurrent::Map.new
12
+ CRITICAL = ::Mutex.new
11
13
 
12
14
 
13
15
  module ClassMethods
14
- # Get default loop
16
+ # Get default reactor
15
17
  #
16
- # @return [::Libuv::Loop]
18
+ # @return [::Libuv::Reactor]
17
19
  def default
18
20
  return @default unless @default.nil?
19
21
  CRITICAL.synchronize {
@@ -21,55 +23,79 @@ module Libuv
21
23
  }
22
24
  end
23
25
 
24
- # Create new Libuv loop
26
+ # Create new Libuv reactor
25
27
  #
26
- # @return [::Libuv::Loop]
27
- def new
28
- return create(::Libuv::Ext.loop_new)
28
+ # @return [::Libuv::Reactor]
29
+ def new(&blk)
30
+ thread = create(::Libuv::Ext.loop_new)
31
+ if block_given?
32
+ ::Thread.new do
33
+ thread.run &blk
34
+ end
35
+ end
36
+ thread
29
37
  end
30
38
 
31
- # Build a Ruby Libuv loop from an existing loop pointer
39
+ # Build a Ruby Libuv reactor from an existing reactor pointer
32
40
  #
33
- # @return [::Libuv::Loop]
41
+ # @return [::Libuv::Reactor]
34
42
  def create(pointer)
35
43
  allocate.tap { |i| i.send(:initialize, FFI::AutoPointer.new(pointer, ::Libuv::Ext.method(:loop_delete))) }
36
44
  end
37
45
 
38
- # Checks for the existence of a loop on the current thread
46
+ # Checks for the existence of a reactor on the current thread
39
47
  #
40
- # @return [::Libuv::Loop | nil]
48
+ # @return [::Libuv::Reactor | nil]
41
49
  def current
42
- LOOPS[Thread.current]
50
+ REACTORS[Thread.current]
43
51
  end
44
52
  end
45
53
  extend ClassMethods
46
54
 
47
55
 
48
- # Initialize a loop using an FFI::Pointer to a libuv loop
56
+ # Initialize a reactor using an FFI::Pointer to a libuv reactor
49
57
  def initialize(pointer) # :notnew:
50
58
  @pointer = pointer
51
- @loop = self
59
+ @reactor = self
60
+ @run_count = 0
61
+ @ref_count = 0
52
62
 
53
63
  # Create an async call for scheduling work from other threads
54
64
  @run_queue = Queue.new
55
- @process_queue = @loop.async method(:process_queue_cb)
65
+ @process_queue = @reactor.async method(:process_queue_cb)
56
66
  @process_queue.unref
57
67
 
58
68
  # Create a next tick timer
59
- @next_tick = @loop.timer method(:next_tick_cb)
69
+ @next_tick = @reactor.timer method(:next_tick_cb)
60
70
  @next_tick.unref
61
71
 
62
- # Create an async call for ending the loop
63
- @stop_loop = @loop.async method(:stop_cb)
64
- @stop_loop.unref
72
+ # Create an async call for ending the reactor
73
+ @stop_reactor = @reactor.async method(:stop_cb)
74
+ @stop_reactor.unref
65
75
 
66
76
  # Libuv can prevent the application shutting down once the main thread has ended
67
77
  # The addition of a prepare function prevents this from happening.
68
- @loop_prep = Libuv::Prepare.new(@loop, method(:noop))
69
- @loop_prep.unref
70
- @loop_prep.start
78
+ @reactor_prep = Libuv::Prepare.new(@reactor, method(:noop))
79
+ @reactor_prep.unref
80
+ @reactor_prep.start
81
+
82
+ # LibUV ingnores program interrupt by default.
83
+ # We provide normal behaviour and allow this to be overriden
84
+ @on_signal = proc { stop_cb }
85
+ sig_callback = method(:signal_cb)
86
+ self.signal(:INT, sig_callback).unref
87
+ self.signal(:HUP, sig_callback).unref
88
+ self.signal(:TERM, sig_callback).unref
89
+
90
+ # Notify of errors
91
+ @throw_on_exit = nil
92
+ @reactor_notify = proc { |error|
93
+ @throw_on_exit = error
94
+ }
71
95
  end
72
96
 
97
+ attr_reader :run_count
98
+
73
99
 
74
100
  protected
75
101
 
@@ -77,12 +103,16 @@ module Libuv
77
103
  def noop; end
78
104
 
79
105
  def stop_cb
80
- LOOPS.delete(@reactor_thread)
106
+ REACTORS.delete(@reactor_thread)
81
107
  @reactor_thread = nil
82
108
 
83
109
  ::Libuv::Ext.stop(@pointer)
84
110
  end
85
111
 
112
+ def signal_cb(_)
113
+ @on_signal.call
114
+ end
115
+
86
116
  def next_tick_cb
87
117
  @next_tick_scheduled = false
88
118
  @next_tick.unref
@@ -94,7 +124,8 @@ module Libuv
94
124
  length = @run_queue.length
95
125
  update_time
96
126
  length.times do
97
- process_item
127
+ # This allows any item to pause its execution without effecting this loop
128
+ ::Fiber.new { process_item }.resume
98
129
  end
99
130
  end
100
131
 
@@ -103,7 +134,7 @@ module Libuv
103
134
  run = @run_queue.pop true # pop non-block
104
135
  run.call
105
136
  rescue Exception => e
106
- @loop.log :error, :next_tick_cb, e
137
+ @reactor.log e, 'performing next tick callback'
107
138
  end
108
139
  end
109
140
 
@@ -120,47 +151,76 @@ module Libuv
120
151
 
121
152
  def handle; @pointer; end
122
153
 
123
- # Run the actual event loop. This method will block until the loop is stopped.
154
+ # Run the actual event reactor. This method will block until the reactor is stopped.
124
155
  #
125
156
  # @param run_type [:UV_RUN_DEFAULT, :UV_RUN_ONCE, :UV_RUN_NOWAIT]
126
157
  # @yieldparam promise [::Libuv::Q::Promise] Yields a promise that can be used for logging unhandled
127
- # exceptions on the loop.
158
+ # exceptions on the reactor.
128
159
  def run(run_type = :UV_RUN_DEFAULT)
129
160
  if @reactor_thread.nil?
130
- @loop_notify = @loop.defer
131
-
132
161
  begin
133
- @reactor_thread = Thread.current
134
- LOOPS[@reactor_thread] = @loop
162
+ @reactor_thread = ::Thread.current
163
+ raise 'only one reactor allowed per-thread' if REACTORS[@reactor_thread]
164
+
165
+ REACTORS[@reactor_thread] = @reactor
166
+ @throw_on_exit = nil
167
+
135
168
  if block_given?
136
169
  update_time
137
- if @@use_fibers
138
- Fiber.new { yield @loop_notify.promise }.resume
139
- else
140
- yield @loop_notify.promise
141
- end
170
+ ::Fiber.new {
171
+ begin
172
+ yield @reactor
173
+ rescue Exception => e
174
+ log(e, 'in reactor run block')
175
+ end
176
+ }.resume
142
177
  end
178
+ @run_count += 1
143
179
  ::Libuv::Ext.run(@pointer, run_type) # This is blocking
144
180
  ensure
181
+ REACTORS.delete(@reactor_thread)
145
182
  @reactor_thread = nil
146
183
  @run_queue.clear
147
184
  end
185
+
186
+ # Raise the last unhandled error to occur on the reactor thread
187
+ raise @throw_on_exit if @throw_on_exit
188
+
148
189
  elsif block_given?
149
- if @@use_fibers
150
- schedule { Fiber.new { yield @loop_notify.promise }.resume }
190
+ if reactor_thread?
191
+ update_time
192
+ yield @reactor
151
193
  else
152
- schedule { yield @loop_notify.promise }
194
+ raise 'reactor already running on another thread'
153
195
  end
154
196
  end
155
- @loop
197
+
198
+ @reactor
199
+ end
200
+
201
+ # Prevents the reactor loop from stopping
202
+ def ref
203
+ if reactor_thread? && reactor_running?
204
+ @process_queue.ref if @ref_count == 0
205
+ @ref_count += 1
206
+ end
207
+ end
208
+
209
+ # Allows the reactor loop to stop
210
+ def unref
211
+ if reactor_thread? && reactor_running? && @ref_count > 0
212
+ @ref_count -= 1
213
+ @process_queue.unref if @ref_count == 0
214
+ end
156
215
  end
157
216
 
158
217
 
159
218
  # Provides a promise notifier for receiving un-handled exceptions
160
219
  #
161
220
  # @return [::Libuv::Q::Promise]
162
- def notifier
163
- @loop_notify.promise
221
+ def notifier(callback = nil, &blk)
222
+ @reactor_notify = callback || blk
223
+ self
164
224
  end
165
225
 
166
226
  # Creates a deferred result object for where the result of an operation may only be returned
@@ -168,7 +228,7 @@ module Libuv
168
228
  #
169
229
  # @return [::Libuv::Q::Deferred]
170
230
  def defer
171
- Q.defer(@loop)
231
+ Q.defer(@reactor)
172
232
  end
173
233
 
174
234
  # Combines multiple promises into a single promise that is resolved when all of the input
@@ -180,7 +240,7 @@ module Libuv
180
240
  # the promises is resolved with a rejection, this resulting promise will be resolved with the
181
241
  # same rejection.
182
242
  def all(*promises)
183
- Q.all(@loop, *promises)
243
+ Q.all(@reactor, *promises)
184
244
  end
185
245
 
186
246
  #
@@ -190,7 +250,7 @@ module Libuv
190
250
  # @param *promises [::Libuv::Q::Promise] a number of promises that will be combined into a single promise
191
251
  # @return [::Libuv::Q::Promise] Returns a single promise
192
252
  def any(*promises)
193
- Q.any(@loop, *promises)
253
+ Q.any(@reactor, *promises)
194
254
  end
195
255
 
196
256
  #
@@ -201,15 +261,16 @@ module Libuv
201
261
  # @return [::Libuv::Q::Promise] Returns a single promise that will be resolved with an array of values,
202
262
  # each [result, wasResolved] value pair corresponding to a at the same index in the `promises` array.
203
263
  def finally(*promises)
204
- Q.finally(@loop, *promises)
264
+ Q.finally(@reactor, *promises)
205
265
  end
206
266
 
207
267
 
208
- # forces loop time update, useful for getting more granular times
268
+ # forces reactor time update, useful for getting more granular times
209
269
  #
210
270
  # @return nil
211
271
  def update_time
212
272
  ::Libuv::Ext.update_time(@pointer)
273
+ self
213
274
  end
214
275
 
215
276
  # Get current time in milliseconds
@@ -228,28 +289,30 @@ module Libuv
228
289
 
229
290
  if name
230
291
  msg = ::Libuv::Ext.strerror(err)
231
- ::Libuv::Error.const_get(name.to_sym).new(msg)
292
+ ::Libuv::Error.const_get(name.to_sym).new("#{msg}, #{name}:#{err}")
232
293
  else
233
294
  # We want a back-trace in this case
234
295
  raise "error lookup failed for code #{err}"
235
296
  end
236
297
  rescue Exception => e
237
- @loop.log :warn, :error_lookup_failed, e
298
+ @reactor.log e, 'performing error lookup'
238
299
  e
239
300
  end
240
301
 
241
302
  # Get a new TCP instance
242
303
  #
243
304
  # @return [::Libuv::TCP]
244
- def tcp
245
- TCP.new(@loop)
305
+ def tcp(callback = nil, &blk)
306
+ callback ||= blk
307
+ TCP.new(@reactor, progress: callback)
246
308
  end
247
309
 
248
310
  # Get a new UDP instance
249
311
  #
250
312
  # @return [::Libuv::UDP]
251
- def udp
252
- UDP.new(@loop)
313
+ def udp(callback = nil, &blk)
314
+ callback ||= blk
315
+ UDP.new(@reactor, progress: callback)
253
316
  end
254
317
 
255
318
  # Get a new TTY instance
@@ -260,7 +323,7 @@ module Libuv
260
323
  def tty(fileno, readable = false)
261
324
  assert_type(Integer, fileno, "io#fileno must return an integer file descriptor, #{fileno.inspect} given")
262
325
 
263
- TTY.new(@loop, fileno, readable)
326
+ TTY.new(@reactor, fileno, readable)
264
327
  end
265
328
 
266
329
  # Get a new Pipe instance
@@ -268,7 +331,7 @@ module Libuv
268
331
  # @param ipc [true, false] indicate if a handle will be used for ipc, useful for sharing tcp socket between processes
269
332
  # @return [::Libuv::Pipe]
270
333
  def pipe(ipc = false)
271
- Pipe.new(@loop, ipc)
334
+ Pipe.new(@reactor, ipc)
272
335
  end
273
336
 
274
337
  # Get a new timer instance
@@ -276,21 +339,21 @@ module Libuv
276
339
  # @param callback [Proc] the callback to be called on timer trigger
277
340
  # @return [::Libuv::Timer]
278
341
  def timer(callback = nil, &blk)
279
- Timer.new(@loop, callback || blk)
342
+ Timer.new(@reactor, callback || blk)
280
343
  end
281
344
 
282
345
  # Get a new Prepare handle
283
346
  #
284
347
  # @return [::Libuv::Prepare]
285
348
  def prepare(callback = nil, &blk)
286
- Prepare.new(@loop, callback || blk)
349
+ Prepare.new(@reactor, callback || blk)
287
350
  end
288
351
 
289
352
  # Get a new Check handle
290
353
  #
291
354
  # @return [::Libuv::Check]
292
355
  def check(callback = nil, &blk)
293
- Check.new(@loop, callback || blk)
356
+ Check.new(@reactor, callback || blk)
294
357
  end
295
358
 
296
359
  # Get a new Idle handle
@@ -298,7 +361,7 @@ module Libuv
298
361
  # @param callback [Proc] the callback to be called on idle trigger
299
362
  # @return [::Libuv::Idle]
300
363
  def idle(callback = nil, &block)
301
- Idle.new(@loop, callback || block)
364
+ Idle.new(@reactor, callback || block)
302
365
  end
303
366
 
304
367
  # Get a new Async handle
@@ -306,7 +369,7 @@ module Libuv
306
369
  # @return [::Libuv::Async]
307
370
  def async(callback = nil, &block)
308
371
  callback ||= block
309
- handle = Async.new(@loop)
372
+ handle = Async.new(@reactor)
310
373
  handle.progress callback if callback
311
374
  handle
312
375
  end
@@ -316,12 +379,18 @@ module Libuv
316
379
  # @return [::Libuv::Signal]
317
380
  def signal(signum = nil, callback = nil, &block)
318
381
  callback ||= block
319
- handle = Signal.new(@loop)
382
+ handle = Signal.new(@reactor)
320
383
  handle.progress callback if callback
321
384
  handle.start(signum) if signum
322
385
  handle
323
386
  end
324
387
 
388
+ # Allows user defined behaviour when sig int is received
389
+ def on_program_interrupt(callback = nil, &block)
390
+ @on_signal = callback || block
391
+ self
392
+ end
393
+
325
394
  # Queue some work for processing in the libuv thread pool
326
395
  #
327
396
  # @param callback [Proc] the callback to be called in the thread pool
@@ -330,7 +399,7 @@ module Libuv
330
399
  def work(callback = nil, &block)
331
400
  callback ||= block
332
401
  assert_block(callback)
333
- Work.new(@loop, callback) # Work is a promise object
402
+ Work.new(@reactor, callback) # Work is a promise object
334
403
  end
335
404
 
336
405
  # Lookup a hostname
@@ -339,10 +408,14 @@ module Libuv
339
408
  # @param port [Integer, String] the service being connected too
340
409
  # @param callback [Proc] the callback to be called on success
341
410
  # @return [::Libuv::Dns]
342
- def lookup(hostname, hint = :IPv4, port = 9, &block)
343
- dns = Dns.new(@loop, hostname, port, hint) # Work is a promise object
344
- dns.then block if block_given?
345
- dns
411
+ def lookup(hostname, hint = :IPv4, port = 9, wait: true, &block)
412
+ dns = Dns.new(@reactor, hostname, port, hint, wait: wait) # Work is a promise object
413
+ if block_given? || !wait
414
+ dns.then block
415
+ dns
416
+ else
417
+ dns.results
418
+ end
346
419
  end
347
420
 
348
421
  # Get a new FSEvent instance
@@ -352,7 +425,7 @@ module Libuv
352
425
  # @raise [ArgumentError] if path is not a string
353
426
  def fs_event(path)
354
427
  assert_type(String, path)
355
- FSEvent.new(@loop, path)
428
+ FSEvent.new(@reactor, path)
356
429
  end
357
430
 
358
431
  # Opens a file and returns an object that can be used to manipulate it
@@ -361,21 +434,21 @@ module Libuv
361
434
  # @param flags [Integer] see ruby File::Constants
362
435
  # @param mode [Integer]
363
436
  # @return [::Libuv::File]
364
- def file(path, flags = 0, mode = 0)
437
+ def file(path, flags = 0, mode: 0, **opts, &blk)
365
438
  assert_type(String, path, "path must be a String")
366
439
  assert_type(Integer, flags, "flags must be an Integer")
367
440
  assert_type(Integer, mode, "mode must be an Integer")
368
- File.new(@loop, path, flags, mode)
441
+ File.new(@reactor, path, flags, mode: mode, **opts, &blk)
369
442
  end
370
443
 
371
444
  # Returns an object for manipulating the filesystem
372
445
  #
373
446
  # @return [::Libuv::Filesystem]
374
447
  def filesystem
375
- Filesystem.new(@loop)
448
+ Filesystem.new(@reactor)
376
449
  end
377
450
 
378
- # Schedule some work to be processed on the event loop as soon as possible (thread safe)
451
+ # Schedule some work to be processed on the event reactor as soon as possible (thread safe)
379
452
  #
380
453
  # @param callback [Proc] the callback to be called on the reactor thread
381
454
  # @raise [ArgumentError] if block is not given
@@ -389,9 +462,11 @@ module Libuv
389
462
  @run_queue << callback
390
463
  @process_queue.call
391
464
  end
465
+
466
+ self
392
467
  end
393
468
 
394
- # Queue some work to be processed in the next iteration of the event loop (thread safe)
469
+ # Queue some work to be processed in the next iteration of the event reactor (thread safe)
395
470
  #
396
471
  # @param callback [Proc] the callback to be called on the reactor thread
397
472
  # @raise [ArgumentError] if block is not given
@@ -410,27 +485,29 @@ module Libuv
410
485
  else
411
486
  @process_queue.call
412
487
  end
488
+
489
+ self
413
490
  end
414
491
 
415
- # Notifies the loop there was an event that should be logged
492
+ # Notifies the reactor there was an event that should be logged
416
493
  #
417
- # @param level [Symbol] the error level (info, warn, error etc)
418
- # @param id [Object] some kind of identifying information
419
- # @param *args [*args] any additional information
420
- def log(level, id, *args)
421
- @loop_notify.notify(level, id, *args)
494
+ # @param error [Exception] the error
495
+ # @param msg [String|nil] optional context on the error
496
+ # @param trace [Array<String>] optional additional trace of caller if async
497
+ def log(error, msg = nil, trace = nil)
498
+ @reactor_notify.call(error, msg, trace)
422
499
  end
423
500
 
424
- # Closes handles opened by the loop class and completes the current loop iteration (thread safe)
501
+ # Closes handles opened by the reactor class and completes the current reactor iteration (thread safe)
425
502
  def stop
426
- @stop_loop.call
503
+ @stop_reactor.call
427
504
  end
428
505
 
429
506
  # True if the calling thread is the same thread as the reactor.
430
507
  #
431
508
  # @return [Boolean]
432
509
  def reactor_thread?
433
- @reactor_thread == Thread.current
510
+ @reactor_thread == ::Thread.current
434
511
  end
435
512
 
436
513
  # Exposed to allow joining on the thread, when run in a multithreaded environment. Performing other actions on the thread has undefined semantics (read: a dangerous endevor).
@@ -438,11 +515,12 @@ module Libuv
438
515
  # @return [Thread]
439
516
  attr_reader :reactor_thread
440
517
 
441
- # Tells you whether the Libuv reactor loop is currently running.
518
+ # Tells you whether the Libuv reactor reactor is currently running.
442
519
  #
443
520
  # @return [Boolean]
444
521
  def reactor_running?
445
522
  !@reactor_thread.nil?
446
523
  end
524
+ alias_method :running?, :reactor_running?
447
525
  end
448
526
  end