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,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTLibuv
4
+ module Stream
5
+
6
+
7
+ def self.included(base)
8
+ base.define_callback function: :on_listen, params: [:pointer, :int]
9
+ base.define_callback function: :write_complete, params: [:pointer, :int]
10
+ base.define_callback function: :on_shutdown, params: [:pointer, :int]
11
+
12
+ base.define_callback function: :on_allocate, params: [:pointer, :size_t, Ext::UvBuf.by_ref]
13
+ base.define_callback function: :on_read, params: [:pointer, :ssize_t, Ext::UvBuf.by_ref]
14
+ end
15
+
16
+
17
+
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"
22
+
23
+
24
+ def listen(backlog)
25
+ return self if @closed
26
+ assert_type(Integer, backlog, BACKLOG_ERROR)
27
+ error = check_result ::MTLibuv::Ext.listen(handle, Integer(backlog), callback(:on_listen))
28
+ reject(error) if error
29
+ self
30
+ end
31
+
32
+ # Starts reading from the handle
33
+ def start_read
34
+ return self if @closed
35
+ error = check_result ::MTLibuv::Ext.read_start(handle, callback(:on_allocate), callback(:on_read))
36
+ reject(error) if error
37
+ self
38
+ end
39
+
40
+ # Stops reading from the handle
41
+ def stop_read
42
+ return self if @closed
43
+ error = check_result ::MTLibuv::Ext.read_stop(handle)
44
+ reject(error) if error
45
+ self
46
+ end
47
+ alias_method :close_read, :stop_read
48
+
49
+ # Shutsdown the writes on the handle waiting until the last write is complete before triggering the callback
50
+ def shutdown
51
+ return self if @closed
52
+ req = ::MTLibuv::Ext.allocate_request_shutdown
53
+ error = check_result ::MTLibuv::Ext.shutdown(req, handle, callback(:on_shutdown, req.address))
54
+ reject(error) if error
55
+ self
56
+ end
57
+
58
+ def try_write(data)
59
+ assert_type(String, data, WRITE_ERROR)
60
+
61
+ buffer1 = ::FFI::MemoryPointer.from_string(data)
62
+ buffer = ::MTLibuv::Ext.buf_init(buffer1, data.respond_to?(:bytesize) ? data.bytesize : data.size)
63
+
64
+ result = ::MTLibuv::Ext.try_write(handle, buffer, 1)
65
+ buffer1.free
66
+
67
+ error = check_result result
68
+ raise error if error
69
+ return result
70
+ end
71
+
72
+ def write(data, wait: false)
73
+ # NOTE:: Similar to udp.rb -> send
74
+ deferred = @reactor.defer
75
+ if !@closed
76
+ begin
77
+ assert_type(String, data, WRITE_ERROR)
78
+
79
+ buffer1 = ::FFI::MemoryPointer.from_string(data)
80
+ buffer = ::MTLibuv::Ext.buf_init(buffer1, data.bytesize)
81
+
82
+ # local as this variable will be available until the handle is closed
83
+ @write_callbacks ||= {}
84
+ req = ::MTLibuv::Ext.allocate_request_write
85
+ @write_callbacks[req.address] = [deferred, buffer1]
86
+ error = check_result ::MTLibuv::Ext.write(req, handle, buffer, 1, callback(:write_complete, req.address))
87
+
88
+ if error
89
+ @write_callbacks.delete req.address
90
+ cleanup_callbacks req.address
91
+
92
+ ::MTLibuv::Ext.free(req)
93
+ buffer1.free
94
+ deferred.reject(error)
95
+
96
+ reject(error) # close the handle
97
+ end
98
+ rescue => e
99
+ deferred.reject(e) # this write exception may not be fatal
100
+ end
101
+ else
102
+ deferred.reject(RuntimeError.new(STREAM_CLOSED_ERROR))
103
+ end
104
+
105
+ if wait
106
+ return deferred.promise if wait == :promise
107
+ deferred.promise.value
108
+ end
109
+
110
+ self
111
+ end
112
+ alias_method :puts, :write
113
+ alias_method :write_nonblock, :write
114
+
115
+ def readable?
116
+ return false if @closed
117
+ ::MTLibuv::Ext.is_readable(handle) > 0
118
+ end
119
+
120
+ def writable?
121
+ return false if @closed
122
+ ::MTLibuv::Ext.is_writable(handle) > 0
123
+ end
124
+
125
+ def progress(&blk)
126
+ @progress = 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
+ @read_defer.promise.value
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
+ @flush_defer.promise.value
162
+ end
163
+
164
+
165
+ private
166
+
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
+
201
+ def on_listen(server, status)
202
+ e = check_result(status)
203
+
204
+ @reactor.exec do
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
213
+ end
214
+ end
215
+ end
216
+
217
+ def on_allocate(client, suggested_size, buffer)
218
+ buffer[:len] = suggested_size
219
+ buffer[:base] = ::MTLibuv::Ext.malloc(suggested_size)
220
+ end
221
+
222
+ def write_complete(req, status)
223
+ deferred, buffer1 = @write_callbacks.delete req.address
224
+ cleanup_callbacks req.address
225
+
226
+ ::MTLibuv::Ext.free(req)
227
+ buffer1.free
228
+
229
+ @reactor.exec do
230
+ resolve deferred, status
231
+ check_flush_buffer if @flush_defer
232
+ end
233
+ end
234
+
235
+ def on_read(handle, nread, buf)
236
+ e = check_result(nread)
237
+ base = buf[:base]
238
+
239
+ if e
240
+ ::MTLibuv::Ext.free(base)
241
+
242
+ @reactor.exec do
243
+ # I assume this is desirable behaviour
244
+ if e.is_a? ::MTLibuv::Error::EOF
245
+ close # Close gracefully
246
+ else
247
+ reject(e)
248
+ end
249
+ end
250
+ else
251
+ data = base.read_string(nread)
252
+ ::MTLibuv::Ext.free(base)
253
+
254
+ @reactor.exec do
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)
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ def on_shutdown(req, status)
269
+ cleanup_callbacks(req.address)
270
+ ::MTLibuv::Ext.free(req)
271
+ @close_error = check_result(status)
272
+
273
+ @reactor.exec { close }
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTLibuv
4
+ class Pipe < Handle
5
+ include Stream
6
+
7
+
8
+ define_callback function: :on_connect, params: [:pointer, :int]
9
+ define_callback function: :write2_complete, params: [:pointer, :int]
10
+
11
+
12
+ WRITE2_ERROR = "data must be a String"
13
+
14
+
15
+ def initialize(reactor, ipc, acceptor = nil)
16
+ @reactor, @ipc = reactor, ipc
17
+
18
+ pipe_ptr = ::MTLibuv::Ext.allocate_handle_pipe
19
+ error = check_result(::MTLibuv::Ext.pipe_init(reactor.handle, pipe_ptr, ipc ? 1 : 0))
20
+ error = check_result(::MTLibuv::Ext.accept(acceptor, pipe_ptr)) if acceptor && error.nil?
21
+
22
+ super(pipe_ptr, error)
23
+ end
24
+
25
+ def bind(name, &callback)
26
+ return if @closed
27
+ @on_accept = callback
28
+ @on_listen = proc { accept }
29
+
30
+ assert_type(String, name, "name must be a String")
31
+ name = windows_path name if FFI::Platform.windows?
32
+
33
+ error = check_result ::MTLibuv::Ext.pipe_bind(handle, name)
34
+ reject(error) if error
35
+
36
+ self
37
+ end
38
+
39
+ def open(fileno)
40
+ assert_type(Integer, fileno, 'fileno must be an integer file descriptor')
41
+
42
+ begin
43
+ raise RuntimeError, CLOSED_HANDLE_ERROR if @closed
44
+ check_result! ::MTLibuv::Ext.pipe_open(handle, fileno)
45
+
46
+ # Emulate on_connect behavior
47
+ if block_given?
48
+ begin
49
+ yield(self)
50
+ rescue Exception => e
51
+ @reactor.log e, 'performing pipe connect callback'
52
+ end
53
+ end
54
+ rescue Exception => e
55
+ reject(e)
56
+ raise e unless block_given?
57
+ end
58
+
59
+ self
60
+ end
61
+
62
+ def connect(name)
63
+ return if @closed
64
+ assert_type(String, name, "name must be a String")
65
+
66
+ begin
67
+ name = windows_path name if FFI::Platform.windows?
68
+ req = ::MTLibuv::Ext.allocate_request_connect
69
+ ::MTLibuv::Ext.pipe_connect(req, handle, name, callback(:on_connect, req.address))
70
+ rescue Exception => e
71
+ reject(e)
72
+ end
73
+
74
+ if block_given?
75
+ @callback = Proc.new
76
+ else
77
+ @coroutine = @reactor.defer
78
+ @coroutine.promise.value
79
+ end
80
+
81
+ self
82
+ end
83
+
84
+ def write2(fd, data = ".", wait: false)
85
+ deferred = @reactor.defer
86
+ if @ipc && !@closed
87
+ begin
88
+ assert_type(String, data, WRITE_ERROR)
89
+ assert_type(Handle, fd, WRITE2_ERROR)
90
+
91
+ size = data.respond_to?(:bytesize) ? data.bytesize : data.size
92
+ buffer = ::MTLibuv::Ext.buf_init(FFI::MemoryPointer.from_string(data), size)
93
+
94
+ # local as this variable will be avaliable until the handle is closed
95
+ req = ::MTLibuv::Ext.allocate_request_write
96
+ @write_callbacks ||= {}
97
+ @write_callbacks[req.address] = deferred
98
+ error = check_result ::MTLibuv::Ext.write2(req, handle, buffer, 1, fd.handle, callback(:write2_complete, req.address))
99
+
100
+ if error
101
+ @write_callbacks.delete(req.address)
102
+ ::MTLibuv::Ext.free(req)
103
+ deferred.reject(error)
104
+
105
+ reject(error) # close the handle
106
+ end
107
+ rescue Exception => e
108
+ deferred.reject(e) # this write exception may not be fatal
109
+ end
110
+ else
111
+ deferred.reject(TypeError.new('pipe not initialized for interprocess communication'))
112
+ end
113
+
114
+ if wait
115
+ return deferred.promise if wait == :promise
116
+ deferred.promise.value
117
+ end
118
+
119
+ self
120
+ end
121
+
122
+ # Windows only
123
+ def pending_instances=(count)
124
+ return 0 if @closed
125
+ assert_type(Integer, count, "count must be an Integer")
126
+ ::MTLibuv::Ext.pipe_pending_instances(handle, count)
127
+ end
128
+
129
+ def check_pending(expecting = nil)
130
+ return nil if ::MTLibuv::Ext.pipe_pending_count(handle) <= 0
131
+
132
+ pending = ::MTLibuv::Ext.pipe_pending_type(handle).to_sym
133
+ raise TypeError, "IPC expecting #{expecting} and received #{pending}" if expecting && expecting.to_sym != pending
134
+
135
+ # Hide the accept logic
136
+ remote = nil
137
+ case pending
138
+ when :tcp
139
+ remote = TCP.new(reactor, handle)
140
+ when :pipe
141
+ remote = Pipe.new(reactor, @ipc, handle)
142
+ else
143
+ raise NotImplementedError, "IPC for handle #{pending} not supported"
144
+ end
145
+ remote
146
+ end
147
+
148
+ def getsockname
149
+ size = 256
150
+ len = FFI::MemoryPointer.new(:size_t)
151
+ len.put_int(0, size)
152
+ buffer = FFI::MemoryPointer.new(size)
153
+ check_result! ::MTLibuv::Ext.pipe_getsockname(handle, buffer, len)
154
+ buffer.read_string
155
+ end
156
+
157
+
158
+ private
159
+
160
+
161
+ def accept
162
+ pipe = nil
163
+ begin
164
+ raise RuntimeError, CLOSED_HANDLE_ERROR if @closed
165
+ pipe = Pipe.new(reactor, @ipc, handle)
166
+ rescue Exception => e
167
+ @reactor.log e, 'pipe accept failed'
168
+ end
169
+ if pipe
170
+ @reactor.exec do
171
+ begin
172
+ @on_accept.call(pipe)
173
+ rescue Exception => e
174
+ @reactor.log e, 'performing pipe accept callback'
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ def on_connect(req, status)
181
+ cleanup_callbacks req.address
182
+ ::MTLibuv::Ext.free(req)
183
+ e = check_result(status)
184
+
185
+ @reactor.exec do
186
+ if e
187
+ reject(e)
188
+ else
189
+ begin
190
+ @callback.call(self)
191
+ rescue Exception => e
192
+ @reactor.log e, 'performing pipe connected callback'
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ def write2_complete(req, status)
199
+ promise = @write_callbacks.delete(req.address)
200
+ cleanup_callbacks req.address
201
+
202
+ ::MTLibuv::Ext.free(req)
203
+
204
+ @reactor.exec do
205
+ resolve promise, status
206
+ end
207
+ end
208
+
209
+ def windows_path(name)
210
+ # test for \\\\.\\pipe
211
+ if not name =~ /(\/|\\){2}\.(\/|\\)pipe/i
212
+ name = ::File.join("\\\\.\\pipe", name)
213
+ end
214
+ name.gsub("/", "\\")
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTLibuv
4
+ class Prepare < Handle
5
+
6
+
7
+ define_callback function: :on_prepare
8
+
9
+
10
+ # @param reactor [::MTLibuv::Reactor] reactor this prepare handle will be associated
11
+ # @param callback [Proc] callback to be called on reactor preparation
12
+ def initialize(reactor)
13
+ @reactor = reactor
14
+
15
+ prepare_ptr = ::MTLibuv::Ext.allocate_handle_prepare
16
+ error = check_result(::MTLibuv::Ext.prepare_init(reactor.handle, prepare_ptr))
17
+
18
+ super(prepare_ptr, error)
19
+ end
20
+
21
+ # Enables the prepare handler.
22
+ def start
23
+ return if @closed
24
+ error = check_result ::MTLibuv::Ext.prepare_start(handle, callback(:on_prepare))
25
+ reject(error) if error
26
+ self
27
+ end
28
+
29
+ # Disables the prepare handler.
30
+ def stop
31
+ return if @closed
32
+ error = check_result ::MTLibuv::Ext.prepare_stop(handle)
33
+ reject(error) if error
34
+ self
35
+ end
36
+
37
+ # Used to update the callback that will be triggered on reactor prepare
38
+ #
39
+ # @param callback [Proc] the callback to be called on reactor prepare
40
+ def progress(&callback)
41
+ @callback = callback
42
+ self
43
+ end
44
+
45
+
46
+ private
47
+
48
+
49
+ def on_prepare(handle)
50
+ @reactor.exec do
51
+ begin
52
+ @callback.call
53
+ rescue Exception => e
54
+ @reactor.log e, 'performing prepare callback'
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end