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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3392e84572469e65fb1505dc8ca99782acca89af
4
- data.tar.gz: bf6311317ca08be77c06a503b4e23b5b574c83e9
3
+ metadata.gz: 328790a2659803772fb2ca5891889bda349b47fd
4
+ data.tar.gz: 0b534aaeeb7fd1fbfd11c35090e8f048be6db411
5
5
  SHA512:
6
- metadata.gz: 64e492f11ddcac4a87cb3460459fc64bcbfa5484b8732346c18d03071d693c7a04281298edd32f851e3787fb3cccef87bc4af08ac06a55182f8f8eaef6e03e81
7
- data.tar.gz: 7a4372cce3b02f884a29cb90a03b3a83bfde40d5b9487a1650185d52df53fb93668898e9a89954dc4c360b9b343f8371df121442d5a8523ad131eee2bc2e1714
6
+ metadata.gz: 00f0aed38c13defc6a2e310a67e81f576fad3d90308d00575982794e4bad6d0f95204b20d5f66db130984e22223c616bc02f78b3ffbefcc2d623c8efec9b1516
7
+ data.tar.gz: da08a1d126e8d25dd0bb8fa8d0deb3a25d05a369725362d2aa5a4fbf5254aa2d5db7f6ec7f10c8b70ad2f24f2f6924018c59259276e83fa4d22eae58554a4599
data/.gitignore CHANGED
@@ -17,3 +17,5 @@ Gemfile-custom
17
17
  ext/*
18
18
 
19
19
  *.gem
20
+
21
+ *.rdb
data/README.md CHANGED
@@ -4,60 +4,93 @@
4
4
 
5
5
  [Libuv](https://github.com/libuv/libuv) is a cross platform asynchronous IO implementation that powers NodeJS. It supports sockets, both UDP and TCP, filesystem watch, TTY, Pipes and other asynchronous primitives like timer, check, prepare and idle.
6
6
 
7
- The Libuv gem contains Libuv and a Ruby wrapper that implements [pipelined promises](http://en.wikipedia.org/wiki/Futures_and_promises#Promise_pipelining) for asynchronous flow control and [coroutines](http://en.wikipedia.org/wiki/Coroutine) for untangling evented code
7
+ The Libuv gem contains Libuv and a Ruby wrapper that implements [pipelined promises](http://en.wikipedia.org/wiki/Futures_and_promises#Promise_pipelining) for asynchronous flow control and [coroutines](http://en.wikipedia.org/wiki/Coroutine) / [futures](https://en.wikipedia.org/wiki/Futures_and_promises) for untangling evented code
8
8
 
9
9
  ## Usage
10
10
 
11
- using promises
11
+ Libuv supports multiple reactors that can run on different threads.
12
+
13
+ For convenience the thread local or default reactor can be accessed via the `reactor` method
14
+ You can pass a block to be executed on the reactor and the reactor will run until there is nothing left to do.
12
15
 
13
16
  ```ruby
14
17
  require 'libuv'
15
18
 
16
- loop = Libuv::Loop.default
17
- # or
18
- # loop = Libuv::Loop.new
19
-
20
- loop.run do
21
- timer = loop.timer do
19
+ reactor do |reactor|
20
+ reactor.timer {
22
21
  puts "5 seconds passed"
23
- timer.close
24
- end
25
- timer.catch do |error|
26
- puts "error with timer: #{error}"
27
- end
28
- timer.finally do
29
- puts "timer handle was closed"
30
- end
31
- timer.start(5000)
22
+ }.start(5000)
32
23
  end
24
+
25
+ puts "reactor stopped. No more IO to process"
33
26
  ```
34
27
 
35
- using coroutines (if a somewhat abstract example)
28
+ Promises are used to simplify code flow.
36
29
 
37
30
  ```ruby
38
31
  require 'libuv'
39
- require 'libuv/coroutines'
40
32
 
41
- loop = Libuv::Loop.default
42
- loop.run do
43
- begin
44
- timer = loop.timer do
45
- puts "5 seconds passed"
46
- timer.close
47
- end
48
- timer.start(5000)
33
+ reactor do |reactor|
34
+ reactor.tcp { |data, socket|
35
+ puts "received: #{data}"
36
+ socket.close
37
+ }
38
+ .connect('127.0.0.1', 3000) { |socket|
39
+ socket.start_read
40
+ .write("GET / HTTP/1.1\r\n\r\n")
41
+ }
42
+ .catch { |error|
43
+ puts "error: #{error}"
44
+ }
45
+ .finally {
46
+ puts "socket closed"
47
+ }
48
+ end
49
+ ```
49
50
 
50
- # co-routine waits for timer to close
51
- co timer
51
+ Continuations are used if callbacks are not defined
52
52
 
53
- puts "timer handle was closed"
53
+ ```ruby
54
+ require 'libuv'
55
+
56
+ reactor do |reactor|
57
+ begin
58
+ reactor.tcp { |data, socket|
59
+ puts "received: #{data}"
60
+ socket.close
61
+ }
62
+ .connect('127.0.0.1', 3000)
63
+ .start_read
64
+ .write("GET / HTTP/1.1\r\n\r\n")
54
65
  rescue => error
55
- puts "error with timer: #{error}"
66
+ puts "error: #{error}"
56
67
  end
57
68
  end
58
69
  ```
59
70
 
60
- Check out the [yard documentation](http://rubydoc.info/gems/libuv/Libuv/Loop)
71
+ Any promise can be converted into a continuation
72
+
73
+ ```ruby
74
+ require 'libuv'
75
+
76
+ reactor do |reactor|
77
+ # Perform work on the thread pool with promises
78
+ reactor.work {
79
+ 10 * 2
80
+ }.then { |result|
81
+ puts "result using a promise #{result}"
82
+ }
83
+
84
+ # Use the coroutine helper to obtain the result without a callback
85
+ result = reactor.work {
86
+ 10 * 3
87
+ }.value
88
+ puts "no additional callbacks here #{result}"
89
+ end
90
+ ```
91
+
92
+
93
+ Check out the [yard documentation](http://rubydoc.info/gems/libuv/Libuv/Reactor)
61
94
 
62
95
 
63
96
  ## Installation
@@ -107,6 +140,6 @@ Windows users will additionally require:
107
140
  * Filesystem Events
108
141
  * Filesystem manipulation
109
142
  * File manipulation
110
- * Errors (with a catch-all fallback for anything unhandled on the event loop)
143
+ * Errors (with a catch-all fallback for anything unhandled on the event reactor)
111
144
  * Work queue (thread pool)
112
- * Coroutines (optional - makes use of Fibers)
145
+ * Coroutines / futures (makes use of Fibers)
data/lib/libuv.rb CHANGED
@@ -1,21 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
4
+ require 'thread'
2
5
  require 'ffi'
3
6
 
4
7
  module Libuv
8
+ DefaultThread = Thread.current
9
+
5
10
  require 'libuv/ext/ext' # The libuv ffi ext
6
11
  require 'libuv/error' # List of errors (matching those in uv.h)
7
12
  require 'libuv/q' # The promise library
8
13
 
9
- # -- The classes required for a loop instance --
14
+ # -- The classes required for a reactor instance --
10
15
  require 'libuv/mixins/assertions' # Common code to check arguments
16
+ require 'libuv/mixins/accessors' # Helper methods for accessing reactor functions
11
17
  require 'libuv/mixins/resource' # Common code to check for errors
12
18
  require 'libuv/mixins/listener' # Common callback code
13
19
 
14
20
  require 'libuv/handle' # Base class for most libuv functionality
15
- require 'libuv/prepare' # Called at the end of a loop cycle
16
- require 'libuv/async' # Provide a threadsafe way to signal the event loop
21
+ require 'libuv/prepare' # Called at the end of a reactor cycle
22
+ require 'libuv/async' # Provide a threadsafe way to signal the event reactor
17
23
  require 'libuv/timer' # High resolution timer
18
- require 'libuv/loop' # The libuv reactor or event loop
24
+ require 'libuv/reactor' # The libuv reactor or event reactor
25
+ require 'libuv/coroutines'
19
26
  # --
20
27
 
21
28
  autoload :FsChecks, 'libuv/mixins/fs_checks' # Common code to check file system results
@@ -25,7 +32,7 @@ module Libuv
25
32
  autoload :Filesystem, 'libuv/filesystem' # Async directory manipulation
26
33
  autoload :FSEvent, 'libuv/fs_event' # Notifies of changes to files and folders as they occur
27
34
  autoload :Signal, 'libuv/signal' # Used to handle OS signals
28
- autoload :Check, 'libuv/check' # Called before processing events on the loop
35
+ autoload :Check, 'libuv/check' # Called before processing events on the reactor
29
36
  autoload :File, 'libuv/file' # Async file reading and writing
30
37
  autoload :Idle, 'libuv/idle' # Called when there are no events to process
31
38
  autoload :Work, 'libuv/work' # Provide work to be completed on another thread (thread pool)
@@ -52,4 +59,22 @@ module Libuv
52
59
  return nil
53
60
  end
54
61
  end
62
+
63
+ # Include all the accessors at this level
64
+ extend Accessors
65
+
66
+ # Run the default reactor
67
+ at_exit do
68
+ reactor = Reactor.default
69
+ reactor.run if $!.nil? && reactor.run_count == 0
70
+ end
71
+ end
72
+
73
+
74
+ class Object
75
+ private
76
+
77
+ def reactor(&blk)
78
+ Libuv.reactor &blk
79
+ end
55
80
  end
data/lib/libuv/async.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Libuv
2
4
  class Async < Handle
3
5
 
@@ -5,14 +7,14 @@ module Libuv
5
7
  define_callback function: :on_async
6
8
 
7
9
 
8
- # @param thread [::Libuv::Loop] loop this async callback will be associated
9
- def initialize(thread, callback = nil, &blk)
10
- @loop = thread
10
+ # @param reactor [::Libuv::Reactor] reactor this async callback will be associated
11
+ def initialize(reactor, callback = nil, &blk)
12
+ @reactor = reactor
11
13
  @callback = callback || blk
12
14
 
13
15
  async_ptr = ::Libuv::Ext.allocate_handle_async
14
16
  on_async = callback(:on_async, async_ptr.address)
15
- error = check_result(::Libuv::Ext.async_init(loop.handle, async_ptr, on_async))
17
+ error = check_result(::Libuv::Ext.async_init(reactor.handle, async_ptr, on_async))
16
18
 
17
19
  super(async_ptr, error)
18
20
  end
@@ -22,13 +24,15 @@ module Libuv
22
24
  return if @closed
23
25
  error = check_result ::Libuv::Ext.async_send(handle)
24
26
  reject(error) if error
27
+ self
25
28
  end
26
29
 
27
30
  # Used to update the callback that will be triggered when async is called
28
31
  #
29
- # @param callback [Proc] the callback to be called on loop prepare
32
+ # @param callback [Proc] the callback to be called on reactor prepare
30
33
  def progress(callback = nil, &blk)
31
34
  @callback = callback || blk
35
+ self
32
36
  end
33
37
 
34
38
 
@@ -36,11 +40,13 @@ module Libuv
36
40
 
37
41
 
38
42
  def on_async(handle)
39
- begin
40
- @callback.call
41
- rescue Exception => e
42
- @loop.log :error, :async_cb, e
43
- end
43
+ ::Fiber.new {
44
+ begin
45
+ @callback.call
46
+ rescue Exception => e
47
+ @reactor.log e, 'performing async callback'
48
+ end
49
+ }.resume
44
50
  end
45
51
  end
46
52
  end
data/lib/libuv/check.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Libuv
2
4
  class Check < Handle
3
5
 
@@ -5,14 +7,14 @@ module Libuv
5
7
  define_callback function: :on_check
6
8
 
7
9
 
8
- # @param loop [::Libuv::Loop] loop this check will be associated
9
- # @param callback [Proc] callback to be called on loop check
10
- def initialize(loop, callback = nil, &blk)
11
- @loop = loop
10
+ # @param reactor [::Libuv::Reactor] reactor this check will be associated
11
+ # @param callback [Proc] callback to be called on reactor check
12
+ def initialize(reactor, callback = nil, &blk)
13
+ @reactor = reactor
12
14
  @callback = callback || blk
13
15
 
14
16
  check_ptr = ::Libuv::Ext.allocate_handle_check
15
- error = check_result(::Libuv::Ext.check_init(loop.handle, check_ptr))
17
+ error = check_result(::Libuv::Ext.check_init(reactor.handle, check_ptr))
16
18
 
17
19
  super(check_ptr, error)
18
20
  end
@@ -22,6 +24,7 @@ module Libuv
22
24
  return if @closed
23
25
  error = check_result ::Libuv::Ext.check_start(handle, callback(:on_check))
24
26
  reject(error) if error
27
+ self
25
28
  end
26
29
 
27
30
  # Disables the check handler.
@@ -29,13 +32,15 @@ module Libuv
29
32
  return if @closed
30
33
  error = check_result ::Libuv::Ext.check_stop(handle)
31
34
  reject(error) if error
35
+ self
32
36
  end
33
37
 
34
- # Used to update the callback that will be triggered on loop check
38
+ # Used to update the callback that will be triggered on reactor check
35
39
  #
36
- # @param callback [Proc] the callback to be called on loop check
40
+ # @param callback [Proc] the callback to be called on reactor check
37
41
  def progress(callback = nil, &blk)
38
42
  @callback = callback || blk
43
+ self
39
44
  end
40
45
 
41
46
 
@@ -43,11 +48,13 @@ module Libuv
43
48
 
44
49
 
45
50
  def on_check(handle)
46
- begin
47
- @callback.call
48
- rescue Exception => e
49
- @loop.log :error, :check_cb, e
50
- end
51
+ ::Fiber.new {
52
+ begin
53
+ @callback.call
54
+ rescue Exception => e
55
+ @reactor.log e, 'performing check callback'
56
+ end
57
+ }.resume
51
58
  end
52
59
  end
53
60
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fiber'
2
4
 
3
- class ::Libuv::Loop
4
- @@use_fibers = true
5
+ class CoroutineRejection < RuntimeError
6
+ attr_accessor :value
5
7
  end
6
8
 
7
9
  class Object
@@ -15,32 +17,57 @@ class Object
15
17
  # @return [Object] Returns the result of a single promise or an array of results if provided multiple promises
16
18
  # @raise [Exception] if the promise is rejected
17
19
  def co(*yieldable, &block)
20
+ on_reactor = Libuv::Reactor.current
21
+ raise 'must be running on a reactor thread to use coroutines' unless on_reactor
22
+
18
23
  f = Fiber.current
19
24
  wasError = false
20
25
 
21
- # Convert the input into a promise
26
+ # Convert the input into a promise on the current reactor
22
27
  if yieldable.length == 1
23
28
  promise = yieldable[0]
29
+ # Passed independently as this is often overwritten for performance
30
+ promise.progress(block) if block_given?
24
31
  else
25
- promise = ::Libuv::Loop.current.all(*yieldable)
32
+ promise = on_reactor.all(*yieldable)
26
33
  end
27
34
 
28
35
  # Use the promise to resume the Fiber
29
36
  promise.then(proc { |res|
30
- f.resume res
37
+ if Libuv::Reactor.current == on_reactor
38
+ f.resume res
39
+ else
40
+ on_reactor.schedule { f.resume(res) }
41
+ end
31
42
  }, proc { |err|
32
43
  wasError = true
33
- f.resume err
44
+ if Libuv::Reactor.current == on_reactor
45
+ f.resume err
46
+ else
47
+ on_reactor.schedule { f.resume(err) }
48
+ end
34
49
  })
35
50
 
36
- # Passed independently as this is often overwritten for performance
37
- promise.progress(block) if block_given?
38
-
39
- # Assign the result from the resume
40
- result = Fiber.yield
51
+ # We want to prevent the reactor from stopping while we are waiting on a response
52
+ on_reactor.ref
53
+ result = Fiber.yield # Assign the result from the resume
54
+ on_reactor.unref
41
55
 
42
56
  # Either return the result or raise an error
43
- raise result if wasError
57
+ if wasError
58
+ if result.is_a?(Exception)
59
+ raise result
60
+ else
61
+ e = case result
62
+ when String, Symbol
63
+ CoroutineRejection.new(result.to_s)
64
+ else
65
+ CoroutineRejection.new
66
+ end
67
+ e.value = result
68
+ raise e
69
+ end
70
+ end
44
71
  result
45
72
  end
46
73
  end
data/lib/libuv/dns.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Libuv
2
4
  class Dns < Q::DeferredPromise
3
5
  include Resource, Listener, Net
@@ -28,11 +30,11 @@ module Libuv
28
30
  end
29
31
 
30
32
 
31
- # @param loop [::Libuv::Loop] loop this work request will be associated
33
+ # @param reactor [::Libuv::Reactor] reactor this work request will be associated
32
34
  # @param domain [String] the domain name to resolve
33
35
  # @param port [Integer, String] the port we wish to use
34
- def initialize(loop, domain, port, hint = :IPv4)
35
- super(loop, loop.defer)
36
+ def initialize(reactor, domain, port, hint = :IPv4, wait: true)
37
+ super(reactor, reactor.defer)
36
38
 
37
39
  @domain = domain
38
40
  @port = port
@@ -42,13 +44,15 @@ module Libuv
42
44
  @error = nil # error in callback
43
45
 
44
46
  @instance_id = @pointer.address
45
- error = check_result ::Libuv::Ext.getaddrinfo(@loop, @pointer, callback(:on_complete), domain, port.to_s, HINTS[hint])
47
+ error = check_result ::Libuv::Ext.getaddrinfo(@reactor, @pointer, callback(:on_complete), domain, port.to_s, HINTS[hint])
46
48
 
47
49
  if error
48
50
  ::Libuv::Ext.free(@pointer)
49
51
  @complete = true
50
52
  @defer.reject(error)
51
53
  end
54
+
55
+ co(@defer.promise) if wait
52
56
  end
53
57
 
54
58
  # Indicates if the lookup has completed yet or not.
@@ -67,22 +71,25 @@ module Libuv
67
71
  ::Libuv::Ext.free(req)
68
72
 
69
73
  e = check_result(status)
70
- if e
71
- @defer.reject(e)
72
- else
73
- begin
74
- current = addrinfo
75
- @results = []
76
- while !current.null?
77
- @results << get_ip_and_port(current[:addr])
78
- current = current[:next]
79
- end
80
- @defer.resolve(@results)
81
- rescue Exception => e
74
+
75
+ ::Fiber.new {
76
+ if e
82
77
  @defer.reject(e)
78
+ else
79
+ begin
80
+ current = addrinfo
81
+ @results = []
82
+ while !current.null?
83
+ @results << get_ip_and_port(current[:addr])
84
+ current = current[:next]
85
+ end
86
+ @defer.resolve(@results)
87
+ rescue Exception => e
88
+ @defer.reject(e)
89
+ end
90
+ ::Libuv::Ext.freeaddrinfo(addrinfo)
83
91
  end
84
- ::Libuv::Ext.freeaddrinfo(addrinfo)
85
- end
92
+ }.resume
86
93
 
87
94
  # Clean up references
88
95
  cleanup_callbacks