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,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