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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libuv
4
+ module Accessors
5
+ def reactor
6
+ thread = Libuv::Reactor.current
7
+ if thread.nil?
8
+ thread = Libuv::Reactor.default
9
+ if thread.reactor_running?
10
+ raise 'No reactor available on this thread'
11
+ end
12
+ end
13
+ thread.run { yield(thread) } if block_given?
14
+ thread
15
+ end
16
+
17
+ Functions = [
18
+ :defer, :all, :any, :finally, :update_time, :now, :lookup_error, :tcp,
19
+ :udp, :tty, :pipe, :timer, :prepare, :check, :idle, :async, :signal,
20
+ :work, :lookup, :fs_event, :file, :filesystem, :schedule, :next_tick,
21
+ :stop, :reactor_thread?, :reactor_running?, :run
22
+ ].freeze
23
+
24
+ Functions.each do |function|
25
+ define_method function do |*args|
26
+ thread = Libuv::Reactor.current
27
+
28
+ if thread
29
+ thread.send(function, *args)
30
+ else
31
+ thread = Libuv::Reactor.default
32
+ if thread.reactor_running?
33
+ raise 'attempted Libuv::Reactor access on non-reactor thread'
34
+ else
35
+ thread.send(function, *args)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Libuv
2
4
  module Assertions
3
- MSG_NO_PROC = 'no block given'.freeze
5
+ MSG_NO_PROC = 'no block given'
4
6
 
5
7
  def assert_block(proc, msg = MSG_NO_PROC)
6
8
  raise ArgumentError, msg, caller unless proc.respond_to? :call
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Libuv
3
4
  module FsChecks
@@ -14,12 +15,27 @@ module Libuv
14
15
  end
15
16
 
16
17
 
17
- def stat
18
- @stat_deferred = @loop.defer
18
+ def stat(wait: true)
19
+ @stat_deferred = @reactor.defer
19
20
 
20
21
  request = ::Libuv::Ext.allocate_request_fs
21
- pre_check @stat_deferred, request, ::Libuv::Ext.fs_fstat(@loop.handle, request, @fileno, callback(:on_stat, request.address))
22
- @stat_deferred.promise
22
+ pre_check @stat_deferred, request, ::Libuv::Ext.fs_fstat(@reactor.handle, request, @fileno, callback(:on_stat, request.address))
23
+ promise = @stat_deferred.promise
24
+
25
+ wait ? co(promise) : promise
26
+ end
27
+
28
+
29
+ protected
30
+
31
+
32
+ def respond(wait, promise)
33
+ if wait
34
+ co(promise)
35
+ self
36
+ else
37
+ promise
38
+ end
23
39
  end
24
40
 
25
41
 
@@ -37,7 +53,7 @@ module Libuv
37
53
  end
38
54
 
39
55
  cleanup(req)
40
- @stat_deferred.resolve(stats)
56
+ ::Fiber.new { @stat_deferred.resolve(stats) }.resume
41
57
  end
42
58
  @stat_deferred = nil
43
59
  end
@@ -63,7 +79,14 @@ module Libuv
63
79
  error = check_result(req[:result])
64
80
  if error
65
81
  cleanup(req)
66
- deferrable.reject(error)
82
+
83
+ ::Fiber.new {
84
+ deferrable.reject(error)
85
+ if @coroutine
86
+ @coroutine.resolve(deferrable.promise)
87
+ @coroutine = nil
88
+ end
89
+ }.resume
67
90
  false
68
91
  else
69
92
  true
@@ -1,4 +1,6 @@
1
- require 'thread_safe'
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
2
4
 
3
5
  module Libuv
4
6
  module Listener
@@ -41,7 +43,7 @@ module Libuv
41
43
 
42
44
  def self.included(base)
43
45
  base.instance_variable_set(:@callback_funcs, {})
44
- base.instance_variable_set(:@callback_lookup, ::ThreadSafe::Cache.new)
46
+ base.instance_variable_set(:@callback_lookup, ::Concurrent::Map.new)
45
47
  base.instance_variable_set(:@callback_lock, ::Mutex.new)
46
48
  base.extend(ClassMethods)
47
49
  end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'socket'
2
4
 
3
5
  module Libuv
4
6
  module Net
5
7
 
6
8
 
7
- IP_ARGUMENT_ERROR = "ip must be a String".freeze # Arguments specifying an IP address
8
- PORT_ARGUMENT_ERROR = "port must be an Integer".freeze # Arguments specifying an IP port
9
+ IP_ARGUMENT_ERROR = "ip must be a String" # Arguments specifying an IP address
10
+ PORT_ARGUMENT_ERROR = "port must be an Integer" # Arguments specifying an IP port
9
11
  INET_ADDRSTRLEN = 16
10
12
  INET6_ADDRSTRLEN = 46
11
13
 
@@ -1,22 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Libuv
2
4
  module Resource
3
5
 
4
6
 
5
7
  def resolve(deferred, rc)
6
8
  if rc && rc < 0
7
- deferred.reject(@loop.lookup_error(rc))
9
+ deferred.reject(@reactor.lookup_error(rc))
8
10
  else
9
11
  deferred.resolve(nil)
10
12
  end
11
13
  end
12
14
 
13
15
  def check_result!(rc)
14
- e = @loop.lookup_error(rc) unless rc.nil? || rc >= 0
16
+ e = @reactor.lookup_error(rc) unless rc.nil? || rc >= 0
15
17
  raise e if e
16
18
  end
17
19
 
18
20
  def check_result(rc)
19
- @loop.lookup_error(rc) unless rc.nil? || rc >= 0
21
+ @reactor.lookup_error(rc) unless rc.nil? || rc >= 0
20
22
  end
21
23
 
22
24
  def to_ptr
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Libuv
2
4
  module Stream
3
5
 
@@ -13,39 +15,44 @@ module Libuv
13
15
 
14
16
 
15
17
 
16
- BACKLOG_ERROR = "backlog must be an Integer".freeze
17
- WRITE_ERROR = "data must be a String".freeze
18
- STREAM_CLOSED_ERROR = "unable to write to a closed stream".freeze
19
- CLOSED_HANDLE_ERROR = "handle closed before accept called".freeze
18
+ BACKLOG_ERROR = "backlog must be an Integer"
19
+ WRITE_ERROR = "data must be a String"
20
+ STREAM_CLOSED_ERROR = "unable to write to a closed stream"
21
+ CLOSED_HANDLE_ERROR = "handle closed before accept called"
20
22
 
21
23
 
22
24
  def listen(backlog)
23
- return if @closed
25
+ return self if @closed
24
26
  assert_type(Integer, backlog, BACKLOG_ERROR)
25
27
  error = check_result ::Libuv::Ext.listen(handle, Integer(backlog), callback(:on_listen))
26
28
  reject(error) if error
29
+ self
27
30
  end
28
31
 
29
32
  # Starts reading from the handle
30
33
  def start_read
31
- return if @closed
34
+ return self if @closed
32
35
  error = check_result ::Libuv::Ext.read_start(handle, callback(:on_allocate), callback(:on_read))
33
36
  reject(error) if error
37
+ self
34
38
  end
35
39
 
36
40
  # Stops reading from the handle
37
41
  def stop_read
38
- return if @closed
42
+ return self if @closed
39
43
  error = check_result ::Libuv::Ext.read_stop(handle)
40
44
  reject(error) if error
45
+ self
41
46
  end
47
+ alias_method :close_read, :stop_read
42
48
 
43
49
  # Shutsdown the writes on the handle waiting until the last write is complete before triggering the callback
44
50
  def shutdown
45
- return if @closed
51
+ return self if @closed
46
52
  req = ::Libuv::Ext.allocate_request_shutdown
47
53
  error = check_result ::Libuv::Ext.shutdown(req, handle, callback(:on_shutdown, req.address))
48
54
  reject(error) if error
55
+ self
49
56
  end
50
57
 
51
58
  def try_write(data)
@@ -62,9 +69,9 @@ module Libuv
62
69
  return result
63
70
  end
64
71
 
65
- def write(data)
72
+ def write(data, wait: false)
66
73
  # NOTE:: Similar to udp.rb -> send
67
- deferred = @loop.defer
74
+ deferred = @reactor.defer
68
75
  if !@closed
69
76
  begin
70
77
  assert_type(String, data, WRITE_ERROR)
@@ -94,9 +101,16 @@ module Libuv
94
101
  else
95
102
  deferred.reject(RuntimeError.new(STREAM_CLOSED_ERROR))
96
103
  end
97
- deferred.promise
104
+
105
+ if wait
106
+ return deferred.promise if wait == :promise
107
+ co deferred.promise
108
+ end
109
+
110
+ self
98
111
  end
99
112
  alias_method :puts, :write
113
+ alias_method :write_nonblock, :write
100
114
 
101
115
  def readable?
102
116
  return false if @closed
@@ -110,24 +124,94 @@ module Libuv
110
124
 
111
125
  def progress(callback = nil, &blk)
112
126
  @progress = callback || blk
127
+ self
128
+ end
129
+
130
+ # Very basic IO emulation, in no way trying to be exact
131
+ def read(maxlen = nil, outbuf = nil)
132
+ raise ::EOFError.new('socket closed') if @closed
133
+ @read_defer = @reactor.defer
134
+
135
+ if @read_buffer.nil?
136
+ start_read
137
+ @read_buffer = String.new
138
+ self.finally do
139
+ @read_defer.reject(::EOFError.new('socket closed'))
140
+ end
141
+ end
142
+
143
+ if check_read_buffer(maxlen, outbuf, @read_defer)
144
+ progress do |data|
145
+ @read_buffer << data
146
+ check_read_buffer(maxlen, outbuf, @read_defer)
147
+ end
148
+ end
149
+
150
+ co @read_defer.promise
151
+ end
152
+ alias_method :read_nonblock, :read
153
+
154
+ # These are here purely for compatibility with rack hijack IO
155
+ def close_write; end
156
+ def flush
157
+ raise ::EOFError.new('socket closed') if @closed
158
+
159
+ @flush_defer = @reactor.defer
160
+ check_flush_buffer
161
+ co @flush_defer.promise
113
162
  end
114
163
 
115
164
 
116
165
  private
117
166
 
118
167
 
168
+ def check_read_buffer(maxlen, outbuf, defer)
169
+ if maxlen && @read_buffer.bytesize >= maxlen
170
+ if outbuf
171
+ outbuf << @read_buffer[0...maxlen]
172
+ defer.resolve outbuf
173
+ else
174
+ defer.resolve @read_buffer[0...maxlen]
175
+ end
176
+ @read_buffer = @read_buffer[maxlen..-1]
177
+ progress do |data|
178
+ @read_buffer << data
179
+ end
180
+ false
181
+ elsif maxlen.nil?
182
+ defer.resolve @read_buffer
183
+ @read_buffer = String.new
184
+ progress do |data|
185
+ @read_buffer << data
186
+ end
187
+ false
188
+ else
189
+ true
190
+ end
191
+ end
192
+
193
+ def check_flush_buffer
194
+ if @flush_defer && (@write_callbacks.nil? || @write_callbacks.empty?) && (@pending_writes.nil? || @pending_writes.empty?) && @pending_write.nil?
195
+ @flush_defer.resolve(nil)
196
+ @flush_defer = nil
197
+ end
198
+ end
199
+
200
+
119
201
  def on_listen(server, status)
120
202
  e = check_result(status)
121
203
 
122
- if e
123
- reject(e) # is this cause for closing the handle?
124
- else
125
- begin
126
- @on_listen.call(self)
127
- rescue Exception => e
128
- @loop.log :error, :stream_listen_cb, e
204
+ ::Fiber.new {
205
+ if e
206
+ reject(e) # is this cause for closing the handle?
207
+ else
208
+ begin
209
+ @on_listen.call(self)
210
+ rescue Exception => e
211
+ @reactor.log e, 'performing stream listening callback'
212
+ end
129
213
  end
130
- end
214
+ }.resume
131
215
  end
132
216
 
133
217
  def on_allocate(client, suggested_size, buffer)
@@ -142,7 +226,10 @@ module Libuv
142
226
  ::Libuv::Ext.free(req)
143
227
  buffer1.free
144
228
 
145
- resolve deferred, status
229
+ ::Fiber.new {
230
+ resolve deferred, status
231
+ check_flush_buffer if @flush_defer
232
+ }.resume
146
233
  end
147
234
 
148
235
  def on_read(handle, nread, buf)
@@ -151,25 +238,30 @@ module Libuv
151
238
 
152
239
  if e
153
240
  ::Libuv::Ext.free(base)
154
- # I assume this is desirable behaviour
155
- if e.is_a? ::Libuv::Error::EOF
156
- close # Close gracefully
157
- else
158
- reject(e)
159
- end
241
+
242
+ ::Fiber.new {
243
+ # I assume this is desirable behaviour
244
+ if e.is_a? ::Libuv::Error::EOF
245
+ close # Close gracefully
246
+ else
247
+ reject(e)
248
+ end
249
+ }.resume
160
250
  else
161
251
  data = base.read_string(nread)
162
252
  ::Libuv::Ext.free(base)
163
253
 
164
- if @tls.nil?
165
- begin
166
- @progress.call data, self
167
- rescue Exception => e
168
- @loop.log :error, :stream_progress_cb, e
254
+ ::Fiber.new {
255
+ if @tls.nil?
256
+ begin
257
+ @progress.call data, self
258
+ rescue Exception => e
259
+ @reactor.log e, 'performing stream read callback'
260
+ end
261
+ else
262
+ @tls.decrypt(data)
169
263
  end
170
- else
171
- @tls.decrypt(data)
172
- end
264
+ }.resume
173
265
  end
174
266
  end
175
267
 
@@ -177,7 +269,8 @@ module Libuv
177
269
  cleanup_callbacks(req.address)
178
270
  ::Libuv::Ext.free(req)
179
271
  @close_error = check_result(status)
180
- close
272
+
273
+ ::Fiber.new { close }.resume
181
274
  end
182
275
  end
183
276
  end
data/lib/libuv/pipe.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Libuv
2
4
  class Pipe < Handle
3
5
  include Stream
@@ -7,14 +9,14 @@ module Libuv
7
9
  define_callback function: :write2_complete, params: [:pointer, :int]
8
10
 
9
11
 
10
- WRITE2_ERROR = "data must be a String".freeze
12
+ WRITE2_ERROR = "data must be a String"
11
13
 
12
14
 
13
- def initialize(loop, ipc, acceptor = nil)
14
- @loop, @ipc = loop, ipc
15
+ def initialize(reactor, ipc, acceptor = nil)
16
+ @reactor, @ipc = reactor, ipc
15
17
 
16
18
  pipe_ptr = ::Libuv::Ext.allocate_handle_pipe
17
- error = check_result(::Libuv::Ext.pipe_init(loop.handle, pipe_ptr, ipc ? 1 : 0))
19
+ error = check_result(::Libuv::Ext.pipe_init(reactor.handle, pipe_ptr, ipc ? 1 : 0))
18
20
  error = check_result(::Libuv::Ext.accept(acceptor, pipe_ptr)) if acceptor && error.nil?
19
21
 
20
22
  super(pipe_ptr, error)
@@ -30,11 +32,13 @@ module Libuv
30
32
 
31
33
  error = check_result ::Libuv::Ext.pipe_bind(handle, name)
32
34
  reject(error) if error
35
+
36
+ self
33
37
  end
34
38
 
35
39
  def open(fileno, callback = nil, &blk)
36
40
  @callback = callback || blk
37
- assert_type(Integer, fileno, 'fileno must be an integer file descriptor'.freeze)
41
+ assert_type(Integer, fileno, 'fileno must be an integer file descriptor')
38
42
 
39
43
  begin
40
44
  raise RuntimeError, CLOSED_HANDLE_ERROR if @closed
@@ -44,11 +48,15 @@ module Libuv
44
48
  begin
45
49
  @callback.call(self) if @callback
46
50
  rescue Exception => e
47
- @loop.log :error, :pipe_connect_cb, e
51
+ @reactor.log e, 'performing pipe connect callback'
52
+ raise e unless @callback
48
53
  end
49
54
  rescue Exception => e
50
55
  reject(e)
56
+ raise e unless @callback
51
57
  end
58
+
59
+ self
52
60
  end
53
61
 
54
62
  def connect(name, callback = nil, &blk)
@@ -62,10 +70,17 @@ module Libuv
62
70
  rescue Exception => e
63
71
  reject(e)
64
72
  end
73
+
74
+ if @callback.nil?
75
+ @coroutine = @reactor.defer
76
+ co @coroutine.promise
77
+ end
78
+
79
+ self
65
80
  end
66
81
 
67
- def write2(fd, data = ".")
68
- deferred = @loop.defer
82
+ def write2(fd, data = ".", wait: false)
83
+ deferred = @reactor.defer
69
84
  if @ipc && !@closed
70
85
  begin
71
86
  assert_type(String, data, WRITE_ERROR)
@@ -93,7 +108,13 @@ module Libuv
93
108
  else
94
109
  deferred.reject(TypeError.new('pipe not initialized for interprocess communication'))
95
110
  end
96
- deferred.promise
111
+
112
+ if wait
113
+ return deferred.promise if wait == :promise
114
+ co deferred.promise
115
+ end
116
+
117
+ self
97
118
  end
98
119
 
99
120
  # Windows only
@@ -113,9 +134,9 @@ module Libuv
113
134
  remote = nil
114
135
  case pending
115
136
  when :tcp
116
- remote = TCP.new(loop, handle)
137
+ remote = TCP.new(reactor, handle)
117
138
  when :pipe
118
- remote = Pipe.new(loop, @ipc, handle)
139
+ remote = Pipe.new(reactor, @ipc, handle)
119
140
  else
120
141
  raise NotImplementedError, "IPC for handle #{pending} not supported"
121
142
  end
@@ -139,16 +160,18 @@ module Libuv
139
160
  pipe = nil
140
161
  begin
141
162
  raise RuntimeError, CLOSED_HANDLE_ERROR if @closed
142
- pipe = Pipe.new(loop, @ipc, handle)
163
+ pipe = Pipe.new(reactor, @ipc, handle)
143
164
  rescue Exception => e
144
- @loop.log :info, :pipe_accept_failed, e
165
+ @reactor.log e, 'pipe accept failed'
145
166
  end
146
167
  if pipe
147
- begin
148
- @on_accept.call(pipe)
149
- rescue Exception => e
150
- @loop.log :error, :pipe_accept_cb, e
151
- end
168
+ ::Fiber.new {
169
+ begin
170
+ @on_accept.call(pipe)
171
+ rescue Exception => e
172
+ @reactor.log e, 'performing pipe accept callback'
173
+ end
174
+ }.resume
152
175
  end
153
176
  end
154
177
 
@@ -157,15 +180,17 @@ module Libuv
157
180
  ::Libuv::Ext.free(req)
158
181
  e = check_result(status)
159
182
 
160
- if e
161
- reject(e)
162
- else
163
- begin
164
- @callback.call(self)
165
- rescue Exception => e
166
- @loop.log :error, :pipe_connect_cb, e
183
+ ::Fiber.new {
184
+ if e
185
+ reject(e)
186
+ else
187
+ begin
188
+ @callback.call(self)
189
+ rescue Exception => e
190
+ @reactor.log e, 'performing pipe connected callback'
191
+ end
167
192
  end
168
- end
193
+ }.resume
169
194
  end
170
195
 
171
196
  def write2_complete(req, status)
@@ -174,7 +199,9 @@ module Libuv
174
199
 
175
200
  ::Libuv::Ext.free(req)
176
201
 
177
- resolve promise, status
202
+ ::Fiber.new {
203
+ resolve promise, status
204
+ }.resume
178
205
  end
179
206
 
180
207
  def windows_path(name)