concurrent-ruby 0.4.1 → 0.5.0.pre.1

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -33
  3. data/lib/concurrent.rb +11 -3
  4. data/lib/concurrent/actor.rb +29 -29
  5. data/lib/concurrent/agent.rb +98 -16
  6. data/lib/concurrent/atomic.rb +125 -0
  7. data/lib/concurrent/channel.rb +36 -1
  8. data/lib/concurrent/condition.rb +67 -0
  9. data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
  10. data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
  11. data/lib/concurrent/count_down_latch.rb +60 -0
  12. data/lib/concurrent/dataflow.rb +85 -0
  13. data/lib/concurrent/dereferenceable.rb +69 -31
  14. data/lib/concurrent/event.rb +27 -21
  15. data/lib/concurrent/future.rb +103 -43
  16. data/lib/concurrent/ivar.rb +78 -0
  17. data/lib/concurrent/mvar.rb +154 -0
  18. data/lib/concurrent/obligation.rb +94 -9
  19. data/lib/concurrent/postable.rb +11 -9
  20. data/lib/concurrent/promise.rb +101 -127
  21. data/lib/concurrent/safe_task_executor.rb +28 -0
  22. data/lib/concurrent/scheduled_task.rb +60 -54
  23. data/lib/concurrent/stoppable.rb +2 -2
  24. data/lib/concurrent/supervisor.rb +36 -29
  25. data/lib/concurrent/thread_local_var.rb +117 -0
  26. data/lib/concurrent/timer_task.rb +28 -30
  27. data/lib/concurrent/utilities.rb +1 -1
  28. data/lib/concurrent/version.rb +1 -1
  29. data/spec/concurrent/agent_spec.rb +121 -230
  30. data/spec/concurrent/atomic_spec.rb +201 -0
  31. data/spec/concurrent/condition_spec.rb +171 -0
  32. data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
  33. data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
  34. data/spec/concurrent/count_down_latch_spec.rb +125 -0
  35. data/spec/concurrent/dataflow_spec.rb +160 -0
  36. data/spec/concurrent/dereferenceable_shared.rb +145 -0
  37. data/spec/concurrent/event_spec.rb +44 -9
  38. data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
  39. data/spec/concurrent/future_spec.rb +184 -69
  40. data/spec/concurrent/ivar_spec.rb +192 -0
  41. data/spec/concurrent/mvar_spec.rb +380 -0
  42. data/spec/concurrent/obligation_spec.rb +193 -0
  43. data/spec/concurrent/observer_set_shared.rb +233 -0
  44. data/spec/concurrent/postable_shared.rb +3 -7
  45. data/spec/concurrent/promise_spec.rb +270 -192
  46. data/spec/concurrent/safe_task_executor_spec.rb +58 -0
  47. data/spec/concurrent/scheduled_task_spec.rb +142 -38
  48. data/spec/concurrent/thread_local_var_spec.rb +113 -0
  49. data/spec/concurrent/thread_pool_shared.rb +2 -3
  50. data/spec/concurrent/timer_task_spec.rb +31 -1
  51. data/spec/spec_helper.rb +2 -3
  52. data/spec/support/functions.rb +4 -0
  53. data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
  54. metadata +50 -30
  55. data/lib/concurrent/contract.rb +0 -21
  56. data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
  57. data/md/actor.md +0 -404
  58. data/md/agent.md +0 -142
  59. data/md/channel.md +0 -40
  60. data/md/dereferenceable.md +0 -49
  61. data/md/future.md +0 -125
  62. data/md/obligation.md +0 -32
  63. data/md/promise.md +0 -217
  64. data/md/scheduled_task.md +0 -156
  65. data/md/supervisor.md +0 -246
  66. data/md/thread_pool.md +0 -225
  67. data/md/timer_task.md +0 -191
  68. data/spec/concurrent/contract_spec.rb +0 -34
  69. data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -0,0 +1,78 @@
1
+ require 'thread'
2
+
3
+ require 'concurrent/obligation'
4
+ require 'concurrent/copy_on_write_observer_set'
5
+
6
+ module Concurrent
7
+
8
+ MultipleAssignmentError = Class.new(StandardError)
9
+
10
+ class IVar
11
+ include Obligation
12
+
13
+ NO_VALUE = Object.new
14
+
15
+ # Create a new +Ivar+ in the +:pending+ state with the (optional) initial value.
16
+ #
17
+ # @param [Object] value the initial value
18
+ # @param [Hash] opts the options to create a message with
19
+ # @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
20
+ # @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
21
+ # @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
22
+ # returning the value returned from the proc
23
+ def initialize(value = NO_VALUE, opts = {})
24
+ init_obligation
25
+ @observers = CopyOnWriteObserverSet.new
26
+ set_deref_options(opts)
27
+
28
+ if value == NO_VALUE
29
+ @state = :pending
30
+ else
31
+ set(value)
32
+ end
33
+ end
34
+
35
+ # Add an observer on this object that will receive notification on update.
36
+ #
37
+ # Upon completion the +IVar+ will notify all observers in a thread-say way. The +func+
38
+ # method of the observer will be called with three arguments: the +Time+ at which the
39
+ # +Future+ completed the asynchronous operation, the final +value+ (or +nil+ on rejection),
40
+ # and the final +reason+ (or +nil+ on fulfillment).
41
+ #
42
+ # @param [Object] observer the object that will be notified of changes
43
+ # @param [Symbol] func symbol naming the method to call when this +Observable+ has changes`
44
+ def add_observer(observer, func = :update)
45
+ direct_notification = false
46
+
47
+ mutex.synchronize do
48
+ if event.set?
49
+ direct_notification = true
50
+ else
51
+ @observers.add_observer(observer, func)
52
+ end
53
+ end
54
+
55
+ observer.send(func, Time.now, self.value, reason) if direct_notification
56
+ func
57
+ end
58
+
59
+ def set(value)
60
+ complete(true, value, nil)
61
+ end
62
+
63
+ def fail(reason = nil)
64
+ complete(false, nil, reason)
65
+ end
66
+
67
+ def complete(success, value, reason)
68
+ mutex.synchronize do
69
+ raise MultipleAssignmentError.new('multiple assignment') if [:fulfilled, :rejected].include? @state
70
+ set_state(success, value, reason)
71
+ event.set
72
+ end
73
+
74
+ time = Time.now
75
+ @observers.notify_and_delete_observers{ [time, self.value, reason] }
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,154 @@
1
+ require 'concurrent/dereferenceable'
2
+ require 'concurrent/event'
3
+
4
+ module Concurrent
5
+
6
+ class MVar
7
+
8
+ include Dereferenceable
9
+
10
+ EMPTY = Object.new
11
+ TIMEOUT = Object.new
12
+
13
+ def initialize(value = EMPTY, opts = {})
14
+ @value = value
15
+ @mutex = Mutex.new
16
+ @empty_condition = Condition.new
17
+ @full_condition = Condition.new
18
+ set_deref_options(opts)
19
+ end
20
+
21
+ def take(timeout = nil)
22
+ @mutex.synchronize do
23
+ wait_for_full(timeout)
24
+
25
+ # If we timed out we'll still be empty
26
+ if unlocked_full?
27
+ value = @value
28
+ @value = EMPTY
29
+ @empty_condition.signal
30
+ apply_deref_options(value)
31
+ else
32
+ TIMEOUT
33
+ end
34
+ end
35
+ end
36
+
37
+ def put(value, timeout = nil)
38
+ @mutex.synchronize do
39
+ wait_for_empty(timeout)
40
+
41
+ # If we timed out we won't be empty
42
+ if unlocked_empty?
43
+ @value = value
44
+ @full_condition.signal
45
+ apply_deref_options(value)
46
+ else
47
+ TIMEOUT
48
+ end
49
+ end
50
+ end
51
+
52
+ def modify(timeout = nil)
53
+ raise ArgumentError.new('no block given') unless block_given?
54
+
55
+ @mutex.synchronize do
56
+ wait_for_full(timeout)
57
+
58
+ # If we timed out we'll still be empty
59
+ if unlocked_full?
60
+ value = @value
61
+ @value = yield value
62
+ @full_condition.signal
63
+ apply_deref_options(value)
64
+ else
65
+ TIMEOUT
66
+ end
67
+ end
68
+ end
69
+
70
+ def try_take!
71
+ @mutex.synchronize do
72
+ if unlocked_full?
73
+ value = @value
74
+ @value = EMPTY
75
+ @empty_condition.signal
76
+ apply_deref_options(value)
77
+ else
78
+ EMPTY
79
+ end
80
+ end
81
+ end
82
+
83
+ def try_put!(value)
84
+ @mutex.synchronize do
85
+ if unlocked_empty?
86
+ @value = value
87
+ @full_condition.signal
88
+ true
89
+ else
90
+ false
91
+ end
92
+ end
93
+ end
94
+
95
+ def set!(value)
96
+ @mutex.synchronize do
97
+ old_value = @value
98
+ @value = value
99
+ @full_condition.signal
100
+ apply_deref_options(old_value)
101
+ end
102
+ end
103
+
104
+ def modify!
105
+ raise ArgumentError.new('no block given') unless block_given?
106
+
107
+ @mutex.synchronize do
108
+ value = @value
109
+ @value = yield value
110
+ if unlocked_empty?
111
+ @empty_condition.signal
112
+ else
113
+ @full_condition.signal
114
+ end
115
+ apply_deref_options(value)
116
+ end
117
+ end
118
+
119
+ def empty?
120
+ @mutex.synchronize { @value == EMPTY }
121
+ end
122
+
123
+ def full?
124
+ not empty?
125
+ end
126
+
127
+ private
128
+
129
+ def unlocked_empty?
130
+ @value == EMPTY
131
+ end
132
+
133
+ def unlocked_full?
134
+ ! unlocked_empty?
135
+ end
136
+
137
+ def wait_for_full(timeout)
138
+ wait_while(@full_condition, timeout) { unlocked_empty? }
139
+ end
140
+
141
+ def wait_for_empty(timeout)
142
+ wait_while(@empty_condition, timeout) { unlocked_full? }
143
+ end
144
+
145
+ def wait_while(condition, timeout)
146
+ remaining = Condition::Result.new(timeout)
147
+ while yield && remaining.can_wait?
148
+ remaining = condition.wait(@mutex, remaining.remaining_time)
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ end
@@ -9,31 +9,116 @@ module Concurrent
9
9
  module Obligation
10
10
  include Dereferenceable
11
11
 
12
- attr_reader :state
13
- attr_reader :reason
14
-
15
12
  # Has the obligation been fulfilled?
16
13
  # @return [Boolean]
17
- def fulfilled?() return(@state == :fulfilled); end
14
+ def fulfilled?
15
+ state == :fulfilled
16
+ end
18
17
  alias_method :realized?, :fulfilled?
19
18
 
20
19
  # Has the obligation been rejected?
21
20
  # @return [Boolean]
22
- def rejected?() return(@state == :rejected); end
21
+ def rejected?
22
+ state == :rejected
23
+ end
23
24
 
24
25
  # Is obligation completion still pending?
25
26
  # @return [Boolean]
26
- def pending?() return(@state == :pending); end
27
+ def pending?
28
+ state == :pending
29
+ end
30
+
31
+ # Is the obligation still unscheduled?
32
+ # @return [Boolean]
33
+ def unscheduled?
34
+ state == :unscheduled
35
+ end
36
+
37
+ def completed?
38
+ [:fulfilled, :rejected].include? state
39
+ end
40
+
41
+ def incomplete?
42
+ [:unscheduled, :pending].include? state
43
+ end
27
44
 
28
45
  def value(timeout = nil)
29
- event.wait(timeout) unless timeout == 0 || @state != :pending
46
+ event.wait(timeout) if timeout != 0 && incomplete?
30
47
  super()
31
48
  end
32
49
 
50
+ def state
51
+ mutex.synchronize { @state }
52
+ end
53
+
54
+ def reason
55
+ mutex.synchronize { @reason }
56
+ end
57
+
33
58
  protected
34
59
 
35
- def event
36
- @event ||= Event.new
60
+ # @!visibility private
61
+ def init_obligation # :nodoc:
62
+ init_mutex
63
+ @event = Event.new
64
+ end
65
+
66
+ # @!visibility private
67
+ def event # :nodoc:
68
+ @event
69
+ end
70
+
71
+ # @!visibility private
72
+ def set_state(success, value, reason) # :nodoc:
73
+ if success
74
+ @value = value
75
+ @state = :fulfilled
76
+ else
77
+ @reason = reason
78
+ @state = :rejected
79
+ end
80
+ end
81
+
82
+ # @!visibility private
83
+ def state=(value) # :nodoc:
84
+ mutex.synchronize { @state = value }
85
+ end
86
+
87
+ # atomic compare and set operation
88
+ # state is set to next_state only if current state is == expected_current
89
+ #
90
+ # @param [Symbol] next_state
91
+ # @param [Symbol] expected_current
92
+ #
93
+ # @return [Boolean] true is state is changed, false otherwise
94
+ #
95
+ # @!visibility private
96
+ def compare_and_set_state(next_state, expected_current) # :nodoc:
97
+ mutex.synchronize do
98
+ if @state == expected_current
99
+ @state = next_state
100
+ true
101
+ else
102
+ false
103
+ end
104
+ end
105
+ end
106
+
107
+ # executes the block within mutex if current state is included in expected_states
108
+ #
109
+ # @return block value if executed, false otherwise
110
+ #
111
+ # @!visibility private
112
+ def if_state(*expected_states) # :nodoc:
113
+ raise ArgumentError.new('no block given') unless block_given?
114
+
115
+ mutex.synchronize do
116
+ if expected_states.include? @state
117
+ yield
118
+ else
119
+ false
120
+ end
121
+ end
37
122
  end
38
123
  end
39
124
  end
@@ -1,3 +1,5 @@
1
+ require 'concurrent/event'
2
+
1
3
  module Concurrent
2
4
 
3
5
  module Postable
@@ -37,20 +39,20 @@ module Concurrent
37
39
  raise ArgumentError.new('empty message') if message.empty?
38
40
  return false unless ready?
39
41
  queue.push(Package.new(message))
40
- return queue.length
42
+ true
41
43
  end
42
44
 
43
45
  def <<(message)
44
46
  post(*message)
45
- return self
47
+ self
46
48
  end
47
49
 
48
50
  def post?(*message)
49
51
  raise ArgumentError.new('empty message') if message.empty?
50
52
  return nil unless ready?
51
- contract = Contract.new
52
- queue.push(Package.new(message, contract))
53
- return contract
53
+ ivar = IVar.new
54
+ queue.push(Package.new(message, ivar))
55
+ ivar
54
56
  end
55
57
 
56
58
  def post!(seconds, *message)
@@ -77,20 +79,20 @@ module Concurrent
77
79
  raise ArgumentError.new('empty message') if message.empty?
78
80
  return false unless ready?
79
81
  queue.push(Package.new(message, receiver))
80
- return queue.length
82
+ queue.length
81
83
  end
82
84
 
83
85
  def ready?
84
86
  if self.respond_to?(:running?) && ! running?
85
- return false
87
+ false
86
88
  else
87
- return true
89
+ true
88
90
  end
89
91
  end
90
92
 
91
93
  private
92
94
 
93
- # @private
95
+ # @!visibility private
94
96
  def queue # :nodoc:
95
97
  @queue ||= Queue.new
96
98
  end
@@ -9,163 +9,137 @@ module Concurrent
9
9
  include Obligation
10
10
  include UsesGlobalThreadPool
11
11
 
12
- # Creates a new promise object. "A promise represents the eventual
13
- # value returned from the single completion of an operation."
14
- # Promises can be chained in a tree structure where each promise
15
- # has zero or more children. Promises are resolved asynchronously
16
- # in the order they are added to the tree. Parents are guaranteed
17
- # to be resolved before their children. The result of each promise
18
- # is passed to each of its children upon resolution. When
19
- # a promise is rejected all its children will be summarily rejected.
20
- # A promise that is neither resolved or rejected is pending.
21
- #
22
- # @param args [Array] zero or more arguments for the block
23
- # @param block [Proc] the block to call when attempting fulfillment
24
- #
25
12
  # @see http://wiki.commonjs.org/wiki/Promises/A
26
13
  # @see http://promises-aplus.github.io/promises-spec/
27
- def initialize(*args, &block)
28
- if args.first.is_a?(Promise)
29
- @parent = args.first
30
- else
31
- @parent = nil
32
- @chain = [self]
33
- end
14
+ def initialize(options = {}, &block)
15
+ options.delete_if {|k, v| v.nil?}
16
+
17
+ @parent = options.fetch(:parent) { nil }
18
+ @on_fulfill = options.fetch(:on_fulfill) { Proc.new{ |result| result } }
19
+ @on_reject = options.fetch(:on_reject) { Proc.new{ |reason| raise reason } }
34
20
 
35
- @lock = Mutex.new
36
- @handler = block || Proc.new{|result| result }
37
- @state = :pending
38
- @value = nil
39
- @reason = nil
40
- @rescued = false
21
+ @promise_body = block || Proc.new{|result| result }
22
+ @state = :unscheduled
41
23
  @children = []
42
- @rescuers = []
43
24
 
44
- init_mutex
45
- realize(*args) if root?
25
+ init_obligation
46
26
  end
47
27
 
48
- def rescued?
49
- return @rescued
28
+ # @return [Promise]
29
+ def self.fulfill(value)
30
+ Promise.new.tap { |p| p.send(:synchronized_set_state!, true, value, nil) }
50
31
  end
51
32
 
52
- # Create a new child Promise. The block argument for the child will
53
- # be the result of fulfilling its parent. If the child will
54
- # immediately be rejected if the parent has already been rejected.
55
- #
56
- # @param block [Proc] the block to call when attempting fulfillment
57
- #
58
- # @return [Promise] the new promise
59
- def then(&block)
60
- child = @lock.synchronize do
61
- block = Proc.new{|result| result } if block.nil?
62
- @children << Promise.new(self, &block)
63
- @children.last.on_reject(@reason) if rejected?
64
- push(@children.last)
65
- @children.last
66
- end
67
- return child
68
- end
69
-
70
- # Add a rescue handler to be run if the promise is rejected (via raised
71
- # exception). Multiple rescue handlers may be added to a Promise.
72
- # Rescue blocks will be checked in order and the first one with a
73
- # matching Exception class will be processed. The block argument
74
- # will be the exception that caused the rejection.
75
- #
76
- # @param clazz [Class] The class of exception to rescue
77
- # @param block [Proc] the block to call if the rescue is matched
78
- #
79
- # @return [self] so that additional chaining can occur
80
- def rescue(clazz = nil, &block)
81
- return self if fulfilled? || rescued? || block.nil?
82
- @lock.synchronize do
83
- rescuer = Rescuer.new(clazz, block)
84
- if pending?
85
- @rescuers << rescuer
86
- else
87
- try_rescue(reason, rescuer)
33
+
34
+ # @return [Promise]
35
+ def self.reject(reason)
36
+ Promise.new.tap { |p| p.send(:synchronized_set_state!, false, nil, reason) }
37
+ end
38
+
39
+ # @return [Promise]
40
+ # @since 0.5.0
41
+ def execute
42
+ if root?
43
+ if compare_and_set_state(:pending, :unscheduled)
44
+ set_pending
45
+ realize(@promise_body)
88
46
  end
47
+ else
48
+ @parent.execute
89
49
  end
90
- return self
50
+ self
51
+ end
52
+
53
+ # @since 0.5.0
54
+ def self.execute(&block)
55
+ new(&block).execute
56
+ end
57
+
58
+
59
+ # @return [Promise] the new promise
60
+ def then(rescuer = nil, &block)
61
+ raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
62
+ block = Proc.new{ |result| result } if block.nil?
63
+ child = Promise.new(parent: self, on_fulfill: block, on_reject: rescuer)
64
+
65
+ mutex.synchronize do
66
+ child.state = :pending if @state == :pending
67
+ child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled
68
+ child.on_reject(@reason) if @state == :rejected
69
+ @children << child
70
+ end
71
+
72
+ child
73
+ end
74
+
75
+ # @return [Promise]
76
+ def on_success(&block)
77
+ raise ArgumentError.new('no block given') unless block_given?
78
+ self.then &block
79
+ end
80
+
81
+ # @return [Promise]
82
+ def rescue(&block)
83
+ self.then(block)
91
84
  end
92
85
  alias_method :catch, :rescue
93
86
  alias_method :on_error, :rescue
94
87
 
95
88
  protected
96
89
 
97
- attr_reader :parent
98
- attr_reader :handler
99
- attr_reader :rescuers
100
-
101
- # @private
102
- Rescuer = Struct.new(:clazz, :block)
90
+ def set_pending
91
+ mutex.synchronize do
92
+ @state = :pending
93
+ @children.each { |c| c.set_pending }
94
+ end
95
+ end
103
96
 
104
- # @private
97
+ # @!visibility private
105
98
  def root? # :nodoc:
106
99
  @parent.nil?
107
100
  end
108
101
 
109
- # @private
110
- def push(promise) # :nodoc:
111
- if root?
112
- @chain << promise
113
- else
114
- @parent.push(promise)
115
- end
102
+ # @!visibility private
103
+ def on_fulfill(result)
104
+ realize Proc.new{ @on_fulfill.call(result) }
105
+ nil
116
106
  end
117
107
 
118
- # @private
119
- def on_fulfill(result) # :nodoc:
120
- @lock.synchronize do
121
- @value = @handler.call(result)
122
- @state = :fulfilled
123
- @reason = nil
124
- end
125
- return self.value
108
+ # @!visibility private
109
+ def on_reject(reason)
110
+ realize Proc.new{ @on_reject.call(reason) }
111
+ nil
126
112
  end
127
113
 
128
- # @private
129
- def on_reject(reason) # :nodoc:
130
- @value = nil
131
- @state = :rejected
132
- @reason = reason
133
- try_rescue(reason)
134
- @children.each{|child| child.on_reject(reason) }
114
+ def notify_child(child)
115
+ if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) }
116
+ if_state(:rejected) { child.on_reject(@reason) }
135
117
  end
136
118
 
137
- # @private
138
- def try_rescue(ex, *rescuers) # :nodoc:
139
- rescuers = @rescuers if rescuers.empty?
140
- rescuer = rescuers.find{|r| r.clazz.nil? || ex.is_a?(r.clazz) }
141
- if rescuer
142
- rescuer.block.call(ex)
143
- @rescued = true
144
- end
145
- rescue Exception => ex
146
- # supress
147
- end
148
-
149
- # @private
150
- def realize(*args) # :nodoc:
151
- Promise.thread_pool.post(@chain, @lock, args) do |chain, lock, args|
152
- result = args.length == 1 ? args.first : args
153
- index = 0
154
- loop do
155
- current = lock.synchronize{ chain[index] }
156
- unless current.rejected?
157
- begin
158
- result = current.on_fulfill(result)
159
- rescue Exception => ex
160
- current.on_reject(ex)
161
- ensure
162
- event.set
163
- end
164
- end
165
- index += 1
166
- Thread.pass while index >= chain.length
119
+ # @!visibility private
120
+ def realize(task)
121
+ Promise.thread_pool.post do
122
+ success, value, reason = SafeTaskExecutor.new( task ).execute
123
+
124
+ children_to_notify = mutex.synchronize do
125
+ set_state!(success, value, reason)
126
+ @children.dup
167
127
  end
128
+
129
+ children_to_notify.each{ |child| notify_child(child) }
168
130
  end
169
131
  end
132
+
133
+ def set_state!(success, value, reason)
134
+ set_state(success, value, reason)
135
+ event.set
136
+ end
137
+
138
+ def synchronized_set_state!(success, value, reason)
139
+ mutex.synchronize do
140
+ set_state!(success, value, reason)
141
+ end
142
+ end
143
+
170
144
  end
171
145
  end