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
data/lib/mt-libuv/q.rb ADDED
@@ -0,0 +1,475 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTLibuv
4
+ module Q
5
+
6
+ # @abstract
7
+ class Promise
8
+ private_class_method :new
9
+
10
+ # This allows subclasses to make use of the catch feature
11
+ alias_method :ruby_catch, :catch
12
+
13
+ # Allows a backtrace to be included in any errors
14
+ attr_accessor :trace
15
+
16
+ #
17
+ # regardless of when the promise was or will be resolved / rejected, calls
18
+ # the error callback asynchronously if the promise is rejected.
19
+ #
20
+ # @param [Proc, &blk] callbacks error, error_block
21
+ # @return [Promise] Returns an unresolved promise for chaining
22
+ def catch(&blk)
23
+ self.then(nil, blk)
24
+ end
25
+
26
+
27
+ def progress(&blk)
28
+ self.then(nil, nil, blk)
29
+ end
30
+
31
+ # A future that provides the value or raises an error if a rejection occurs
32
+ def value
33
+ ::MTLibuv.co(self)
34
+ end
35
+
36
+ #
37
+ # allows you to observe either the fulfillment or rejection of a promise, but to do so
38
+ # without modifying the final value. This is useful to release resources or do some
39
+ # clean-up that needs to be done whether the promise was rejected or resolved.
40
+ #
41
+ # @param [Proc, &blk] callbacks finally, finally_block
42
+ # @return [Promise] Returns an unresolved promise for chaining
43
+ def finally
44
+ handleCallback = lambda { |value, isResolved|
45
+ callbackOutput = nil
46
+ begin
47
+ callbackOutput = yield
48
+ rescue Exception => e
49
+ @reactor.log e, 'performing promise finally callback', @trace
50
+ return make_promise(e, false, @reactor)
51
+ end
52
+
53
+ if callbackOutput.is_a?(Promise)
54
+ return callbackOutput.then(proc {
55
+ make_promise(value, isResolved, @reactor)
56
+ }, proc { |err|
57
+ make_promise(err, false, @reactor)
58
+ })
59
+ else
60
+ return make_promise(value, isResolved, @reactor)
61
+ end
62
+ }
63
+
64
+ self.then(proc {|val|
65
+ handleCallback.call(val, true)
66
+ }, proc{|err|
67
+ handleCallback.call(err, false)
68
+ })
69
+ end
70
+
71
+ protected
72
+
73
+ def make_promise(value, resolved, reactor)
74
+ result = Q.defer(reactor)
75
+ if (resolved)
76
+ result.resolve(value)
77
+ else
78
+ result.reject(value)
79
+ end
80
+ result.promise
81
+ end
82
+ end
83
+
84
+
85
+ #
86
+ # A new promise instance is created when a deferred instance is created and can be
87
+ # retrieved by calling deferred.promise
88
+ #
89
+ class DeferredPromise < Promise
90
+ public_class_method :new
91
+
92
+ def initialize(reactor, defer)
93
+ raise ArgumentError unless defer.is_a?(Deferred)
94
+ super()
95
+
96
+ @reactor = reactor
97
+ @defer = defer
98
+ end
99
+
100
+ #
101
+ # regardless of when the promise was or will be resolved / rejected, calls one of
102
+ # the success or error callbacks asynchronously as soon as the result is available.
103
+ # The callbacks are called with a single argument, the result or rejection reason.
104
+ #
105
+ # @param [Proc, Proc, Proc, &blk] callbacks error, success, progress, success_block
106
+ # @return [Promise] Returns an unresolved promise for chaining
107
+ def then(callback = nil, errback = nil, progback = nil)
108
+ result = Q.defer(@reactor)
109
+ callback = Proc.new if block_given?
110
+
111
+ wrappedCallback = proc { |val|
112
+ begin
113
+ result.resolve(callback ? callback.call(val) : val)
114
+ rescue Exception => e
115
+ result.reject(e)
116
+ @reactor.log e, 'performing promise resolution callback', @trace
117
+ end
118
+ }
119
+
120
+ wrappedErrback = proc { |reason|
121
+ begin
122
+ result.resolve(errback ? errback.call(reason) : Q.reject(@reactor, reason))
123
+ rescue Exception => e
124
+ result.reject(e)
125
+ @reactor.log e, 'performing promise rejection callback', @trace
126
+ end
127
+ }
128
+
129
+ wrappedProgback = proc { |*progress|
130
+ begin
131
+ result.notify(progback ? progback.call(*progress) : progress)
132
+ rescue Exception => e
133
+ @reactor.log e, 'performing promise progress callback', @trace
134
+ end
135
+ }
136
+
137
+ #
138
+ # Schedule as we are touching shared state
139
+ # Everything else is locally scoped
140
+ #
141
+ @reactor.schedule do
142
+ pending_array = pending
143
+
144
+ if pending_array.nil?
145
+ reference.then(wrappedCallback, wrappedErrback, wrappedProgback)
146
+ else
147
+ pending_array << [wrappedCallback, wrappedErrback, wrappedProgback]
148
+ end
149
+ end
150
+
151
+ result.promise
152
+ end
153
+
154
+ def resolved?
155
+ pending.nil?
156
+ end
157
+
158
+
159
+ private
160
+
161
+
162
+ def pending
163
+ @defer.pending
164
+ end
165
+
166
+ def reference
167
+ @defer.reference
168
+ end
169
+ end
170
+
171
+
172
+
173
+ class ResolvedPromise < Promise
174
+ public_class_method :new
175
+
176
+ def initialize(reactor, response, error = false)
177
+ raise ArgumentError if error && response.is_a?(Promise)
178
+ super()
179
+
180
+ @reactor = reactor
181
+ @error = error
182
+ @response = response
183
+ end
184
+
185
+ def then(callback = nil, errback = nil, progback = nil)
186
+ result = Q.defer(@reactor)
187
+ callback = Proc.new if block_given?
188
+
189
+ @reactor.next_tick {
190
+ if @error
191
+ begin
192
+ result.resolve(errback ? errback.call(@response) : Q.reject(@reactor, @response))
193
+ rescue Exception => e
194
+ result.reject(e)
195
+ @reactor.log e, 'performing promise rejection callback', @trace
196
+ end
197
+ else
198
+ begin
199
+ result.resolve(callback ? callback.call(@response) : @response)
200
+ rescue Exception => e
201
+ result.reject(e)
202
+ @reactor.log e, 'performing promise resolution callback', @trace
203
+ end
204
+ end
205
+ }
206
+
207
+ result.promise
208
+ end
209
+
210
+ def resolved?
211
+ true
212
+ end
213
+ end
214
+
215
+
216
+ #
217
+ # The purpose of the deferred object is to expose the associated Promise instance as well
218
+ # as APIs that can be used for signalling the successful or unsuccessful completion of a task.
219
+ #
220
+ class Deferred
221
+ include Q
222
+
223
+ def initialize(reactor)
224
+ super()
225
+
226
+ @pending = []
227
+ @reference = nil
228
+ @reactor = reactor
229
+ end
230
+
231
+ attr_reader :pending, :reference
232
+
233
+ #
234
+ # resolves the derived promise with the value. If the value is a rejection constructed via
235
+ # Q.reject, the promise will be rejected instead.
236
+ #
237
+ # @param [Object] val constant, message or an object representing the result.
238
+ def resolve(val = nil)
239
+ @reactor.schedule do
240
+ if not @pending.nil?
241
+ callbacks = @pending
242
+ @pending = nil
243
+ @reference = ref(@reactor, val)
244
+
245
+ if callbacks.length > 0
246
+ callbacks.each do |callback|
247
+ @reference.then(callback[0], callback[1], callback[2])
248
+ end
249
+ end
250
+ end
251
+ end
252
+ self
253
+ end
254
+
255
+ #
256
+ # rejects the derived promise with the reason. This is equivalent to resolving it with a rejection
257
+ # constructed via Q.reject.
258
+ #
259
+ # @param [Object] reason constant, message, exception or an object representing the rejection reason.
260
+ def reject(reason = nil)
261
+ resolve(Q.reject(@reactor, reason))
262
+ end
263
+
264
+ #
265
+ # Creates a promise object associated with this deferred
266
+ #
267
+ def promise
268
+ @promise ||= DeferredPromise.new(@reactor, self)
269
+ @promise # Should only ever be one per deferred
270
+ end
271
+
272
+ #
273
+ # Provides an asynchronous callback mechanism
274
+ #
275
+ # @param [*Object] data you would like to send down the promise chain.
276
+ def notify(*args)
277
+ @reactor.schedule do # just in case we are on a different event reactor
278
+ if @pending && @pending.length > 0
279
+ callbacks = @pending
280
+ @reactor.next_tick do
281
+ callbacks.each do |callback|
282
+ callback[2].call(*args)
283
+ end
284
+ end
285
+ end
286
+ end
287
+ self
288
+ end
289
+
290
+ def resolved?
291
+ @pending.nil?
292
+ end
293
+
294
+ def value
295
+ ::MTLibuv.co(self.promise)
296
+ end
297
+
298
+ # Overwrite to prevent inspecting errors hanging the VM
299
+ def inspect
300
+ if @pending.nil?
301
+ "#<#{self.class}:0x#{self.__id__.to_s(16)} @reactor=#{@reactor.inspect} @reference=#{@reference.inspect}>"
302
+ else
303
+ "#<#{self.class}:0x#{self.__id__.to_s(16)} @reactor=#{@reactor.inspect} @pending.count=#{@pending.length}>"
304
+ end
305
+ end
306
+ end
307
+
308
+
309
+
310
+
311
+
312
+
313
+ #
314
+ # Creates a Deferred object which represents a task which will finish in the future.
315
+ #
316
+ # @return [Deferred] Returns a new instance of Deferred
317
+ def defer(reactor)
318
+ return Deferred.new(reactor)
319
+ end
320
+
321
+
322
+ #
323
+ # Creates a promise that is resolved as rejected with the specified reason. This api should be
324
+ # used to forward rejection in a chain of promises. If you are dealing with the last promise in
325
+ # a promise chain, you don't need to worry about it.
326
+ #
327
+ # When comparing deferreds/promises to the familiar behaviour of try/catch/throw, think of
328
+ # reject as the raise keyword in Ruby. This also means that if you "catch" an error via
329
+ # a promise error callback and you want to forward the error to the promise derived from the
330
+ # current promise, you have to "rethrow" the error by returning a rejection constructed via
331
+ # reject.
332
+ #
333
+ # @example handling rejections
334
+ #
335
+ # #!/usr/bin/env ruby
336
+ #
337
+ # require 'rubygems' # or use Bundler.setup
338
+ # require 'em-promise'
339
+ #
340
+ # promiseB = promiseA.then(lambda {|reason|
341
+ # # error: handle the error if possible and resolve promiseB with newPromiseOrValue,
342
+ # # otherwise forward the rejection to promiseB
343
+ # if canHandle(reason)
344
+ # # handle the error and recover
345
+ # return newPromiseOrValue
346
+ # end
347
+ # return Q.reject(reactor, reason)
348
+ # }, lambda {|result|
349
+ # # success: do something and resolve promiseB with the old or a new result
350
+ # return result
351
+ # })
352
+ #
353
+ # @param [Object] reason constant, message, exception or an object representing the rejection reason.
354
+ # @return [Promise] Returns a promise that was already resolved as rejected with the reason
355
+ def reject(reactor, reason = nil)
356
+ return ResolvedPromise.new(reactor, reason, true) # A resolved failed promise
357
+ end
358
+
359
+ #
360
+ # Combines multiple promises into a single promise that is resolved when all of the input
361
+ # promises are resolved.
362
+ #
363
+ # @param [*Promise] Promises a number of promises that will be combined into a single promise
364
+ # @return [Promise] Returns a single promise that will be resolved with an array of values,
365
+ # each value corresponding to the promise at the same index in the `promises` array. If any of
366
+ # the promises is resolved with a rejection, this resulting promise will be resolved with the
367
+ # same rejection.
368
+ def all(reactor, *promises)
369
+ deferred = Q.defer(reactor)
370
+ promises = promises.flatten
371
+ counter = promises.length
372
+ results = []
373
+
374
+ if counter > 0
375
+ promises.each_index do |index|
376
+ ref(reactor, promises[index]).then(proc {|result|
377
+ if results[index].nil?
378
+ results[index] = result
379
+ counter -= 1
380
+ deferred.resolve(results) if counter <= 0
381
+ end
382
+ result
383
+ }, proc {|reason|
384
+ if results[index].nil?
385
+ deferred.reject(reason)
386
+ end
387
+ Q.reject(@reactor, reason) # Don't modify result
388
+ })
389
+ end
390
+ else
391
+ deferred.resolve(results)
392
+ end
393
+
394
+ return deferred.promise
395
+ end
396
+
397
+
398
+ #
399
+ # Combines multiple promises into a single promise that is resolved when any of the input
400
+ # promises are resolved.
401
+ #
402
+ # @param [*Promise] Promises a number of promises that will be combined into a single promise
403
+ # @return [Promise] Returns a single promise
404
+ def any(reactor, *promises)
405
+ deferred = Q.defer(reactor)
406
+ promises = promises.flatten
407
+ if promises.length > 0
408
+ promises.each_index do |index|
409
+ ref(reactor, promises[index]).then(proc { |result|
410
+ deferred.resolve(result)
411
+ }, proc { |reason|
412
+ deferred.reject(reason)
413
+ Q.reject(@reactor, reason) # Don't modify result
414
+ })
415
+ end
416
+ else
417
+ deferred.resolve(true)
418
+ end
419
+ deferred.promise
420
+ end
421
+
422
+
423
+ #
424
+ # Combines multiple promises into a single promise that is resolved when all of the input
425
+ # promises are resolved or rejected.
426
+ #
427
+ # @param [*Promise] Promises a number of promises that will be combined into a single promise
428
+ # @return [Promise] Returns a single promise that will be resolved with an array of values,
429
+ # each [result, wasResolved] value pair corresponding to a at the same index in the `promises` array.
430
+ def self.finally(reactor, *promises)
431
+ deferred = Q.defer(reactor)
432
+ promises = promises.flatten
433
+ counter = promises.length
434
+ results = []
435
+
436
+ if counter > 0
437
+ promises.each_index do |index|
438
+ ref(reactor, promises[index]).then(proc {|result|
439
+ if results[index].nil?
440
+ results[index] = [result, true]
441
+ counter -= 1
442
+ deferred.resolve(results) if counter <= 0
443
+ end
444
+ result
445
+ }, proc {|reason|
446
+ if results[index].nil?
447
+ results[index] = [reason, false]
448
+ counter -= 1
449
+ deferred.resolve(results) if counter <= 0
450
+ end
451
+ Q.reject(@reactor, reason) # Don't modify result
452
+ })
453
+ end
454
+ else
455
+ deferred.resolve(results)
456
+ end
457
+
458
+ return deferred.promise
459
+ end
460
+
461
+
462
+ private
463
+
464
+
465
+ def ref(reactor, value)
466
+ return value if value.is_a?(Promise)
467
+ return ResolvedPromise.new(reactor, value) # A resolved success promise
468
+ end
469
+
470
+
471
+ module_function :all, :reject, :defer, :ref, :any
472
+ private_class_method :ref
473
+ end
474
+
475
+ end