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