concurrent-ruby 0.4.1 → 0.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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