concurrent-ruby 0.0.1 → 0.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.
- checksums.yaml +4 -4
- data/README.md +223 -3
- data/lib/concurrent.rb +19 -0
- data/lib/concurrent/agent.rb +130 -0
- data/lib/concurrent/cached_thread_pool.rb +122 -0
- data/lib/concurrent/defer.rb +69 -0
- data/lib/concurrent/event.rb +60 -0
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -0
- data/lib/concurrent/fixed_thread_pool.rb +89 -0
- data/lib/concurrent/future.rb +42 -0
- data/lib/concurrent/global_thread_pool.rb +3 -0
- data/lib/concurrent/goroutine.rb +25 -0
- data/lib/concurrent/obligation.rb +121 -0
- data/lib/concurrent/promise.rb +194 -0
- data/lib/concurrent/thread_pool.rb +61 -0
- data/lib/concurrent/version.rb +1 -1
- data/lib/concurrent_ruby.rb +1 -0
- data/md/agent.md +123 -0
- data/md/defer.md +174 -0
- data/md/event.md +32 -0
- data/md/future.md +83 -0
- data/md/goroutine.md +52 -0
- data/md/obligation.md +32 -0
- data/md/promise.md +225 -0
- data/md/thread_pool.md +197 -0
- data/spec/concurrent/agent_spec.rb +405 -0
- data/spec/concurrent/cached_thread_pool_spec.rb +112 -0
- data/spec/concurrent/defer_spec.rb +199 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +246 -0
- data/spec/concurrent/event_spec.rb +134 -0
- data/spec/concurrent/fixed_thread_pool_spec.rb +84 -0
- data/spec/concurrent/future_spec.rb +115 -0
- data/spec/concurrent/goroutine_spec.rb +52 -0
- data/spec/concurrent/obligation_shared.rb +121 -0
- data/spec/concurrent/promise_spec.rb +310 -0
- data/spec/concurrent/thread_pool_shared.rb +209 -0
- data/spec/spec_helper.rb +2 -0
- metadata +61 -4
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/global_thread_pool'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
|
7
|
+
IllegalMethodCallError = Class.new(StandardError)
|
8
|
+
|
9
|
+
class Defer
|
10
|
+
|
11
|
+
def initialize(operation = nil, callback = nil, errorback = nil, &block)
|
12
|
+
raise ArgumentError.new('no operation given') if operation.nil? && ! block_given?
|
13
|
+
raise ArgumentError.new('two operations given') if ! operation.nil? && block_given?
|
14
|
+
|
15
|
+
@operation = operation || block
|
16
|
+
@callback = callback
|
17
|
+
@errorback = errorback
|
18
|
+
|
19
|
+
if operation.nil?
|
20
|
+
@running = false
|
21
|
+
else
|
22
|
+
self.go
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def then(&block)
|
27
|
+
raise IllegalMethodCallError.new('a callback has already been provided') unless @callback.nil?
|
28
|
+
raise IllegalMethodCallError.new('the defer is already running') if @running
|
29
|
+
raise ArgumentError.new('no block given') unless block_given?
|
30
|
+
@callback = block
|
31
|
+
return self
|
32
|
+
end
|
33
|
+
|
34
|
+
def rescue(&block)
|
35
|
+
raise IllegalMethodCallError.new('a errorback has already been provided') unless @errorback.nil?
|
36
|
+
raise IllegalMethodCallError.new('the defer is already running') if @running
|
37
|
+
raise ArgumentError.new('no block given') unless block_given?
|
38
|
+
@errorback = block
|
39
|
+
return self
|
40
|
+
end
|
41
|
+
alias_method :catch, :rescue
|
42
|
+
alias_method :on_error, :rescue
|
43
|
+
|
44
|
+
def go
|
45
|
+
return nil if @running
|
46
|
+
@running = true
|
47
|
+
$GLOBAL_THREAD_POOL.post { fulfill }
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# @private
|
54
|
+
def fulfill # :nodoc:
|
55
|
+
result = @operation.call
|
56
|
+
@callback.call(result) unless @callback.nil?
|
57
|
+
rescue Exception => ex
|
58
|
+
@errorback.call(ex) unless @errorback.nil?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module Kernel
|
64
|
+
|
65
|
+
def defer(*args, &block)
|
66
|
+
return Concurrent::Defer.new(*args, &block)
|
67
|
+
end
|
68
|
+
module_function :defer
|
69
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
class Event
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@set = false
|
10
|
+
@notifier = Queue.new
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@waiting = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def set?
|
16
|
+
return @set == true
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(pulse = false)
|
20
|
+
return true if set?
|
21
|
+
@mutex.synchronize {
|
22
|
+
@set = true
|
23
|
+
while @waiting > 0
|
24
|
+
@notifier << :set
|
25
|
+
@waiting -= 1
|
26
|
+
end
|
27
|
+
@set = ! pulse
|
28
|
+
}
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
|
32
|
+
def reset
|
33
|
+
@mutex.synchronize {
|
34
|
+
@set = false
|
35
|
+
}
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
|
39
|
+
def pulse
|
40
|
+
return set(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
def wait(timeout = nil)
|
44
|
+
return true if set?
|
45
|
+
|
46
|
+
if timeout.nil?
|
47
|
+
@waiting += 1
|
48
|
+
@notifier.pop
|
49
|
+
else
|
50
|
+
Timeout::timeout(timeout) do
|
51
|
+
@waiting += 1
|
52
|
+
@notifier.pop
|
53
|
+
end
|
54
|
+
end
|
55
|
+
return true
|
56
|
+
rescue Timeout::Error
|
57
|
+
return false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'concurrent/global_thread_pool'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
class EventMachineDeferProxy
|
6
|
+
behavior(:global_thread_pool)
|
7
|
+
|
8
|
+
def post(*args, &block)
|
9
|
+
if args.empty?
|
10
|
+
EventMachine.defer(block)
|
11
|
+
else
|
12
|
+
new_block = proc{ block.call(*args) }
|
13
|
+
EventMachine.defer(new_block)
|
14
|
+
end
|
15
|
+
return true
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(block)
|
19
|
+
EventMachine.defer(block)
|
20
|
+
return self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/thread_pool'
|
4
|
+
require 'concurrent/event'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
def self.new_fixed_thread_pool(size)
|
9
|
+
return FixedThreadPool.new(size)
|
10
|
+
end
|
11
|
+
|
12
|
+
class FixedThreadPool < ThreadPool
|
13
|
+
behavior(:thread_pool)
|
14
|
+
|
15
|
+
MIN_POOL_SIZE = 1
|
16
|
+
MAX_POOL_SIZE = 1024
|
17
|
+
|
18
|
+
def initialize(size)
|
19
|
+
super()
|
20
|
+
if size < MIN_POOL_SIZE || size > MAX_POOL_SIZE
|
21
|
+
raise ArgumentError.new("size must be between #{MIN_POOL_SIZE} and #{MAX_POOL_SIZE}")
|
22
|
+
end
|
23
|
+
|
24
|
+
@pool = size.times.collect{ create_worker_thread }
|
25
|
+
collect_garbage
|
26
|
+
end
|
27
|
+
|
28
|
+
def kill
|
29
|
+
@status = :killed
|
30
|
+
@pool.each{|t| Thread.kill(t) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
if running?
|
35
|
+
return @pool.length
|
36
|
+
else
|
37
|
+
return 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def post(*args, &block)
|
42
|
+
raise ArgumentError.new('no block given') unless block_given?
|
43
|
+
if running?
|
44
|
+
@queue << [args, block]
|
45
|
+
return true
|
46
|
+
else
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @private
|
52
|
+
def status # :nodoc:
|
53
|
+
@pool.collect{|t| t.status }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# @private
|
59
|
+
def create_worker_thread # :nodoc:
|
60
|
+
Thread.new do
|
61
|
+
loop do
|
62
|
+
task = @queue.pop
|
63
|
+
if task == :stop
|
64
|
+
break
|
65
|
+
else
|
66
|
+
task.last.call(*task.first)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@pool.delete(Thread.current)
|
70
|
+
if @pool.empty?
|
71
|
+
@termination.set
|
72
|
+
@status = :shutdown unless killed?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @private
|
78
|
+
def collect_garbage # :nodoc:
|
79
|
+
@collector = Thread.new do
|
80
|
+
sleep(1)
|
81
|
+
@pool.size.times do |i|
|
82
|
+
if @pool[i].status.nil?
|
83
|
+
@pool[i] = create_worker_thread
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/obligation'
|
4
|
+
require 'concurrent/global_thread_pool'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
class Future
|
9
|
+
include Obligation
|
10
|
+
behavior(:future)
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
|
14
|
+
unless block_given?
|
15
|
+
@state = :fulfilled
|
16
|
+
else
|
17
|
+
@value = nil
|
18
|
+
@state = :pending
|
19
|
+
$GLOBAL_THREAD_POOL.post do
|
20
|
+
semaphore.synchronize do
|
21
|
+
Thread.pass
|
22
|
+
begin
|
23
|
+
@value = yield(*args)
|
24
|
+
@state = :fulfilled
|
25
|
+
rescue Exception => ex
|
26
|
+
@state = :rejected
|
27
|
+
@reason = ex
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Kernel
|
37
|
+
|
38
|
+
def future(*args, &block)
|
39
|
+
return Concurrent::Future.new(*args, &block)
|
40
|
+
end
|
41
|
+
module_function :future
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/global_thread_pool'
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
|
7
|
+
# Post the given agruments and block to the Global Thread Pool.
|
8
|
+
#
|
9
|
+
# @param args [Array] zero or more arguments for the block
|
10
|
+
# @param block [Proc] operation to be performed concurrently
|
11
|
+
#
|
12
|
+
# @return [true,false] success/failre of thread creation
|
13
|
+
#
|
14
|
+
# @note Althought based on Go's goroutines and Erlang's spawn/1,
|
15
|
+
# Ruby has a vastly different runtime. Threads aren't nearly as
|
16
|
+
# efficient in Ruby. Use this function appropriately.
|
17
|
+
#
|
18
|
+
# @see http://golang.org/doc/effective_go.html#goroutines
|
19
|
+
# @see https://gobyexample.com/goroutines
|
20
|
+
def go(*args, &block)
|
21
|
+
return false unless block_given?
|
22
|
+
$GLOBAL_THREAD_POOL.post(*args, &block)
|
23
|
+
end
|
24
|
+
module_function :go
|
25
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
require 'functional/behavior'
|
5
|
+
|
6
|
+
behavior_info(:future,
|
7
|
+
state: 0,
|
8
|
+
value: -1,
|
9
|
+
reason: 0,
|
10
|
+
pending?: 0,
|
11
|
+
fulfilled?: 0,
|
12
|
+
rejected?: 0)
|
13
|
+
|
14
|
+
behavior_info(:promise,
|
15
|
+
state: 0,
|
16
|
+
value: -1,
|
17
|
+
reason: 0,
|
18
|
+
pending?: 0,
|
19
|
+
fulfilled?: 0,
|
20
|
+
rejected?: 0,
|
21
|
+
then: 0,
|
22
|
+
rescue: -1)
|
23
|
+
|
24
|
+
module Concurrent
|
25
|
+
|
26
|
+
module Obligation
|
27
|
+
|
28
|
+
attr_reader :state
|
29
|
+
attr_reader :reason
|
30
|
+
|
31
|
+
# Has the obligation been fulfilled?
|
32
|
+
# @return [Boolean]
|
33
|
+
def fulfilled?() return(@state == :fulfilled); end
|
34
|
+
alias_method :realized?, :fulfilled?
|
35
|
+
|
36
|
+
# Is obligation completion still pending?
|
37
|
+
# @return [Boolean]
|
38
|
+
def pending?() return(!(fulfilled? || rejected?)); end
|
39
|
+
|
40
|
+
def value(timeout = nil)
|
41
|
+
if !pending? || timeout == 0
|
42
|
+
return @value
|
43
|
+
elsif timeout.nil?
|
44
|
+
return semaphore.synchronize { @value }
|
45
|
+
else
|
46
|
+
begin
|
47
|
+
return Timeout::timeout(timeout.to_f) {
|
48
|
+
semaphore.synchronize { @value }
|
49
|
+
}
|
50
|
+
rescue Timeout::Error => ex
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
alias_method :deref, :value
|
56
|
+
|
57
|
+
# Has the promise been rejected?
|
58
|
+
# @return [Boolean]
|
59
|
+
def rejected?() return(@state == :rejected); end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def semaphore
|
64
|
+
@semaphore ||= Mutex.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module Kernel
|
70
|
+
|
71
|
+
def deref(obligation, timeout = nil)
|
72
|
+
if obligation.respond_to?(:deref)
|
73
|
+
return obligation.deref(timeout)
|
74
|
+
elsif obligation.respond_to?(:value)
|
75
|
+
return obligation.deref(timeout)
|
76
|
+
else
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
module_function :deref
|
81
|
+
|
82
|
+
def pending?(obligation)
|
83
|
+
if obligation.respond_to?(:pending?)
|
84
|
+
return obligation.pending?
|
85
|
+
else
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
module_function :pending?
|
90
|
+
|
91
|
+
def fulfilled?(obligation)
|
92
|
+
if obligation.respond_to?(:fulfilled?)
|
93
|
+
return obligation.fulfilled?
|
94
|
+
elsif obligation.respond_to?(:realized?)
|
95
|
+
return obligation.realized?
|
96
|
+
else
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
module_function :fulfilled?
|
101
|
+
|
102
|
+
def realized?(obligation)
|
103
|
+
if obligation.respond_to?(:realized?)
|
104
|
+
return obligation.realized?
|
105
|
+
elsif obligation.respond_to?(:fulfilled?)
|
106
|
+
return obligation.fulfilled?
|
107
|
+
else
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
module_function :realized?
|
112
|
+
|
113
|
+
def rejected?(obligation)
|
114
|
+
if obligation.respond_to?(:rejected?)
|
115
|
+
return obligation.rejected?
|
116
|
+
else
|
117
|
+
return false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
module_function :rejected?
|
121
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/obligation'
|
4
|
+
require 'concurrent/global_thread_pool'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
class Promise
|
9
|
+
include Obligation
|
10
|
+
behavior(:future)
|
11
|
+
behavior(:promise)
|
12
|
+
|
13
|
+
# Creates a new promise object. "A promise represents the eventual
|
14
|
+
# value returned from the single completion of an operation."
|
15
|
+
# Promises can be chained in a tree structure where each promise
|
16
|
+
# has zero or more children. Promises are resolved asynchronously
|
17
|
+
# in the order they are added to the tree. Parents are guaranteed
|
18
|
+
# to be resolved before their children. The result of each promise
|
19
|
+
# is passed to each of its children upon resolution. When
|
20
|
+
# a promise is rejected all its children will be summarily rejected.
|
21
|
+
# A promise that is neither resolved or rejected is pending.
|
22
|
+
#
|
23
|
+
# @param args [Array] zero or more arguments for the block
|
24
|
+
# @param block [Proc] the block to call when attempting fulfillment
|
25
|
+
#
|
26
|
+
# @see http://wiki.commonjs.org/wiki/Promises/A
|
27
|
+
# @see http://promises-aplus.github.io/promises-spec/
|
28
|
+
def initialize(*args, &block)
|
29
|
+
if args.first.is_a?(Promise)
|
30
|
+
@parent = args.first
|
31
|
+
else
|
32
|
+
@parent = nil
|
33
|
+
@chain = [self]
|
34
|
+
end
|
35
|
+
|
36
|
+
@mutex = Mutex.new
|
37
|
+
@handler = block || Proc.new{|result| result }
|
38
|
+
@state = :pending
|
39
|
+
@value = nil
|
40
|
+
@reason = nil
|
41
|
+
@children = []
|
42
|
+
@rescuers = []
|
43
|
+
|
44
|
+
realize(*args) if root?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create a new child Promise. The block argument for the child will
|
48
|
+
# be the result of fulfilling its parent. If the child will
|
49
|
+
# immediately be rejected if the parent has already been rejected.
|
50
|
+
#
|
51
|
+
# @param block [Proc] the block to call when attempting fulfillment
|
52
|
+
#
|
53
|
+
# @return [Promise] the new promise
|
54
|
+
def then(&block)
|
55
|
+
child = @mutex.synchronize do
|
56
|
+
block = Proc.new{|result| result } unless block_given?
|
57
|
+
@children << Promise.new(self, &block)
|
58
|
+
@children.last.on_reject(@reason) if rejected?
|
59
|
+
push(@children.last)
|
60
|
+
@children.last
|
61
|
+
end
|
62
|
+
return child
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add a rescue handler to be run if the promise is rejected (via raised
|
66
|
+
# exception). Multiple rescue handlers may be added to a Promise.
|
67
|
+
# Rescue blocks will be checked in order and the first one with a
|
68
|
+
# matching Exception class will be processed. The block argument
|
69
|
+
# will be the exception that caused the rejection.
|
70
|
+
#
|
71
|
+
# @param clazz [Class] The class of exception to rescue
|
72
|
+
# @param block [Proc] the block to call if the rescue is matched
|
73
|
+
#
|
74
|
+
# @return [self] so that additional chaining can occur
|
75
|
+
def rescue(clazz = Exception, &block)
|
76
|
+
@mutex.synchronize do
|
77
|
+
@rescuers << Rescuer.new(clazz, block) if block_given?
|
78
|
+
end
|
79
|
+
return self
|
80
|
+
end
|
81
|
+
alias_method :catch, :rescue
|
82
|
+
alias_method :on_error, :rescue
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
attr_reader :parent
|
87
|
+
attr_reader :handler
|
88
|
+
attr_reader :rescuers
|
89
|
+
|
90
|
+
# @private
|
91
|
+
Rescuer = Struct.new(:clazz, :block)
|
92
|
+
|
93
|
+
# @private
|
94
|
+
def root # :nodoc:
|
95
|
+
current = self
|
96
|
+
current = current.parent until current.root?
|
97
|
+
return current
|
98
|
+
end
|
99
|
+
|
100
|
+
# @private
|
101
|
+
def root? # :nodoc:
|
102
|
+
@parent.nil?
|
103
|
+
end
|
104
|
+
|
105
|
+
# @private
|
106
|
+
def push(promise) # :nodoc:
|
107
|
+
if root?
|
108
|
+
@chain << promise
|
109
|
+
else
|
110
|
+
@parent.push(promise)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @private
|
115
|
+
def on_fulfill(value) # :nodoc:
|
116
|
+
@mutex.synchronize do
|
117
|
+
if pending?
|
118
|
+
@value = @handler.call(value)
|
119
|
+
@state = :fulfilled
|
120
|
+
@reason = nil
|
121
|
+
end
|
122
|
+
end
|
123
|
+
return @value
|
124
|
+
end
|
125
|
+
|
126
|
+
# @private
|
127
|
+
def on_reject(reason) # :nodoc:
|
128
|
+
@mutex.synchronize do
|
129
|
+
if pending?
|
130
|
+
@state = :rejected
|
131
|
+
@reason = reason
|
132
|
+
self.try_rescue(reason)
|
133
|
+
@value = nil
|
134
|
+
end
|
135
|
+
@children.each{|child| child.on_reject(reason) }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @private
|
140
|
+
def try_rescue(ex) # :nodoc:
|
141
|
+
rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
|
142
|
+
rescuer.block.call(ex) if rescuer
|
143
|
+
rescue Exception => e
|
144
|
+
# supress
|
145
|
+
end
|
146
|
+
|
147
|
+
# @private
|
148
|
+
def realize(*args) # :nodoc:
|
149
|
+
$GLOBAL_THREAD_POOL.post(@chain, @mutex, args) do |chain, mutex, args|
|
150
|
+
result = args.length == 1 ? args.first : args
|
151
|
+
index = 0
|
152
|
+
loop do
|
153
|
+
Thread.pass
|
154
|
+
current = mutex.synchronize{ chain[index] }
|
155
|
+
unless current.rejected?
|
156
|
+
current.semaphore.synchronize do
|
157
|
+
begin
|
158
|
+
result = current.on_fulfill(result)
|
159
|
+
rescue Exception => ex
|
160
|
+
current.on_reject(ex)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
index += 1
|
165
|
+
sleep while index >= chain.length
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
module Kernel
|
173
|
+
|
174
|
+
# Creates a new promise object. "A promise represents the eventual
|
175
|
+
# value returned from the single completion of an operation."
|
176
|
+
# Promises can be chained in a tree structure where each promise
|
177
|
+
# has zero or more children. Promises are resolved asynchronously
|
178
|
+
# in the order they are added to the tree. Parents are guaranteed
|
179
|
+
# to be resolved before their children. The result of each promise
|
180
|
+
# is passes to each of its children when the child resolves. When
|
181
|
+
# a promise is rejected all its children will be summarily rejected.
|
182
|
+
# A promise added to a rejected promise will immediately be rejected.
|
183
|
+
# A promise that is neither resolved or rejected is pending.
|
184
|
+
#
|
185
|
+
# @param args [Array] zero or more arguments for the block
|
186
|
+
# @param block [Proc] the block to call when attempting fulfillment
|
187
|
+
#
|
188
|
+
# @see Promise
|
189
|
+
# @see http://wiki.commonjs.org/wiki/Promises/A
|
190
|
+
def promise(*args, &block)
|
191
|
+
return Concurrent::Promise.new(*args, &block)
|
192
|
+
end
|
193
|
+
module_function :promise
|
194
|
+
end
|