concurrent-ruby 0.6.0.pre.1 → 0.6.0.pre.2

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/lib/concurrent.rb +9 -29
  4. data/lib/concurrent/{actor.rb → actor/actor.rb} +3 -3
  5. data/lib/concurrent/actor/actor_context.rb +77 -0
  6. data/lib/concurrent/actor/actor_ref.rb +67 -0
  7. data/lib/concurrent/{postable.rb → actor/postable.rb} +1 -1
  8. data/lib/concurrent/actor/simple_actor_ref.rb +94 -0
  9. data/lib/concurrent/actors.rb +5 -0
  10. data/lib/concurrent/agent.rb +81 -47
  11. data/lib/concurrent/async.rb +35 -35
  12. data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
  13. data/lib/concurrent/atomic/atomic_fixnum.rb +170 -0
  14. data/lib/concurrent/{condition.rb → atomic/condition.rb} +0 -0
  15. data/lib/concurrent/{copy_on_notify_observer_set.rb → atomic/copy_on_notify_observer_set.rb} +48 -13
  16. data/lib/concurrent/{copy_on_write_observer_set.rb → atomic/copy_on_write_observer_set.rb} +41 -20
  17. data/lib/concurrent/atomic/count_down_latch.rb +116 -0
  18. data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
  19. data/lib/concurrent/atomic/event.rb +103 -0
  20. data/lib/concurrent/{thread_local_var.rb → atomic/thread_local_var.rb} +0 -0
  21. data/lib/concurrent/atomics.rb +9 -0
  22. data/lib/concurrent/channel/buffered_channel.rb +6 -4
  23. data/lib/concurrent/channel/channel.rb +30 -2
  24. data/lib/concurrent/channel/unbuffered_channel.rb +2 -2
  25. data/lib/concurrent/channel/waitable_list.rb +3 -1
  26. data/lib/concurrent/channels.rb +5 -0
  27. data/lib/concurrent/{channel → collection}/blocking_ring_buffer.rb +16 -5
  28. data/lib/concurrent/collection/priority_queue.rb +305 -0
  29. data/lib/concurrent/{channel → collection}/ring_buffer.rb +6 -1
  30. data/lib/concurrent/collections.rb +3 -0
  31. data/lib/concurrent/configuration.rb +68 -19
  32. data/lib/concurrent/dataflow.rb +9 -9
  33. data/lib/concurrent/delay.rb +21 -13
  34. data/lib/concurrent/dereferenceable.rb +40 -33
  35. data/lib/concurrent/exchanger.rb +3 -0
  36. data/lib/concurrent/{cached_thread_pool.rb → executor/cached_thread_pool.rb} +8 -9
  37. data/lib/concurrent/executor/executor.rb +222 -0
  38. data/lib/concurrent/{fixed_thread_pool.rb → executor/fixed_thread_pool.rb} +6 -7
  39. data/lib/concurrent/{immediate_executor.rb → executor/immediate_executor.rb} +5 -5
  40. data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
  41. data/lib/concurrent/{java_fixed_thread_pool.rb → executor/java_fixed_thread_pool.rb} +7 -11
  42. data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
  43. data/lib/concurrent/{java_thread_pool_executor.rb → executor/java_thread_pool_executor.rb} +66 -77
  44. data/lib/concurrent/executor/one_by_one.rb +65 -0
  45. data/lib/concurrent/{per_thread_executor.rb → executor/per_thread_executor.rb} +4 -4
  46. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
  47. data/lib/concurrent/{ruby_fixed_thread_pool.rb → executor/ruby_fixed_thread_pool.rb} +5 -4
  48. data/lib/concurrent/executor/ruby_single_thread_executor.rb +72 -0
  49. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +282 -0
  50. data/lib/concurrent/{ruby_thread_pool_worker.rb → executor/ruby_thread_pool_worker.rb} +6 -6
  51. data/lib/concurrent/{safe_task_executor.rb → executor/safe_task_executor.rb} +20 -13
  52. data/lib/concurrent/executor/single_thread_executor.rb +35 -0
  53. data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
  54. data/lib/concurrent/executor/timer_set.rb +138 -0
  55. data/lib/concurrent/executors.rb +9 -0
  56. data/lib/concurrent/future.rb +39 -40
  57. data/lib/concurrent/ivar.rb +22 -15
  58. data/lib/concurrent/mvar.rb +2 -1
  59. data/lib/concurrent/obligation.rb +9 -3
  60. data/lib/concurrent/observable.rb +33 -0
  61. data/lib/concurrent/options_parser.rb +46 -0
  62. data/lib/concurrent/promise.rb +23 -24
  63. data/lib/concurrent/scheduled_task.rb +21 -45
  64. data/lib/concurrent/timer_task.rb +204 -126
  65. data/lib/concurrent/tvar.rb +1 -1
  66. data/lib/concurrent/utilities.rb +3 -36
  67. data/lib/concurrent/{processor_count.rb → utility/processor_count.rb} +1 -1
  68. data/lib/concurrent/utility/timeout.rb +36 -0
  69. data/lib/concurrent/utility/timer.rb +21 -0
  70. data/lib/concurrent/version.rb +1 -1
  71. data/lib/concurrent_ruby_ext.bundle +0 -0
  72. data/spec/concurrent/{actor_context_spec.rb → actor/actor_context_spec.rb} +0 -8
  73. data/spec/concurrent/{actor_ref_shared.rb → actor/actor_ref_shared.rb} +9 -59
  74. data/spec/concurrent/{actor_spec.rb → actor/actor_spec.rb} +43 -41
  75. data/spec/concurrent/{postable_shared.rb → actor/postable_shared.rb} +0 -0
  76. data/spec/concurrent/actor/simple_actor_ref_spec.rb +135 -0
  77. data/spec/concurrent/agent_spec.rb +160 -71
  78. data/spec/concurrent/atomic/atomic_boolean_spec.rb +172 -0
  79. data/spec/concurrent/atomic/atomic_fixnum_spec.rb +186 -0
  80. data/spec/concurrent/{condition_spec.rb → atomic/condition_spec.rb} +2 -2
  81. data/spec/concurrent/{copy_on_notify_observer_set_spec.rb → atomic/copy_on_notify_observer_set_spec.rb} +0 -0
  82. data/spec/concurrent/{copy_on_write_observer_set_spec.rb → atomic/copy_on_write_observer_set_spec.rb} +0 -0
  83. data/spec/concurrent/atomic/count_down_latch_spec.rb +151 -0
  84. data/spec/concurrent/atomic/cyclic_barrier_spec.rb +248 -0
  85. data/spec/concurrent/{event_spec.rb → atomic/event_spec.rb} +18 -3
  86. data/spec/concurrent/{observer_set_shared.rb → atomic/observer_set_shared.rb} +15 -6
  87. data/spec/concurrent/{thread_local_var_spec.rb → atomic/thread_local_var_spec.rb} +0 -0
  88. data/spec/concurrent/channel/buffered_channel_spec.rb +1 -1
  89. data/spec/concurrent/channel/channel_spec.rb +6 -4
  90. data/spec/concurrent/channel/probe_spec.rb +37 -9
  91. data/spec/concurrent/channel/unbuffered_channel_spec.rb +2 -2
  92. data/spec/concurrent/{channel → collection}/blocking_ring_buffer_spec.rb +0 -0
  93. data/spec/concurrent/collection/priority_queue_spec.rb +317 -0
  94. data/spec/concurrent/{channel → collection}/ring_buffer_spec.rb +0 -0
  95. data/spec/concurrent/configuration_spec.rb +4 -70
  96. data/spec/concurrent/dereferenceable_shared.rb +5 -4
  97. data/spec/concurrent/exchanger_spec.rb +10 -5
  98. data/spec/concurrent/{cached_thread_pool_shared.rb → executor/cached_thread_pool_shared.rb} +15 -37
  99. data/spec/concurrent/{fixed_thread_pool_shared.rb → executor/fixed_thread_pool_shared.rb} +0 -0
  100. data/spec/concurrent/{global_thread_pool_shared.rb → executor/global_thread_pool_shared.rb} +10 -8
  101. data/spec/concurrent/{immediate_executor_spec.rb → executor/immediate_executor_spec.rb} +0 -0
  102. data/spec/concurrent/{java_cached_thread_pool_spec.rb → executor/java_cached_thread_pool_spec.rb} +1 -21
  103. data/spec/concurrent/{java_fixed_thread_pool_spec.rb → executor/java_fixed_thread_pool_spec.rb} +0 -0
  104. data/spec/concurrent/executor/java_single_thread_executor_spec.rb +21 -0
  105. data/spec/concurrent/{java_thread_pool_executor_spec.rb → executor/java_thread_pool_executor_spec.rb} +0 -0
  106. data/spec/concurrent/{per_thread_executor_spec.rb → executor/per_thread_executor_spec.rb} +0 -4
  107. data/spec/concurrent/{ruby_cached_thread_pool_spec.rb → executor/ruby_cached_thread_pool_spec.rb} +1 -1
  108. data/spec/concurrent/{ruby_fixed_thread_pool_spec.rb → executor/ruby_fixed_thread_pool_spec.rb} +0 -0
  109. data/spec/concurrent/executor/ruby_single_thread_executor_spec.rb +18 -0
  110. data/spec/concurrent/{ruby_thread_pool_executor_spec.rb → executor/ruby_thread_pool_executor_spec.rb} +12 -24
  111. data/spec/concurrent/executor/safe_task_executor_spec.rb +103 -0
  112. data/spec/concurrent/{thread_pool_class_cast_spec.rb → executor/thread_pool_class_cast_spec.rb} +12 -0
  113. data/spec/concurrent/{thread_pool_executor_shared.rb → executor/thread_pool_executor_shared.rb} +0 -0
  114. data/spec/concurrent/{thread_pool_shared.rb → executor/thread_pool_shared.rb} +84 -119
  115. data/spec/concurrent/executor/timer_set_spec.rb +183 -0
  116. data/spec/concurrent/future_spec.rb +12 -0
  117. data/spec/concurrent/ivar_spec.rb +11 -1
  118. data/spec/concurrent/observable_shared.rb +173 -0
  119. data/spec/concurrent/observable_spec.rb +51 -0
  120. data/spec/concurrent/options_parser_spec.rb +71 -0
  121. data/spec/concurrent/runnable_shared.rb +6 -0
  122. data/spec/concurrent/scheduled_task_spec.rb +60 -40
  123. data/spec/concurrent/timer_task_spec.rb +130 -144
  124. data/spec/concurrent/{processor_count_spec.rb → utility/processor_count_spec.rb} +0 -0
  125. data/spec/concurrent/{utilities_spec.rb → utility/timeout_spec.rb} +0 -0
  126. data/spec/concurrent/utility/timer_spec.rb +52 -0
  127. metadata +147 -108
  128. data/lib/concurrent/actor_context.rb +0 -31
  129. data/lib/concurrent/actor_ref.rb +0 -39
  130. data/lib/concurrent/atomic.rb +0 -121
  131. data/lib/concurrent/channel/probe.rb +0 -19
  132. data/lib/concurrent/count_down_latch.rb +0 -60
  133. data/lib/concurrent/event.rb +0 -80
  134. data/lib/concurrent/java_cached_thread_pool.rb +0 -45
  135. data/lib/concurrent/ruby_cached_thread_pool.rb +0 -37
  136. data/lib/concurrent/ruby_thread_pool_executor.rb +0 -268
  137. data/lib/concurrent/simple_actor_ref.rb +0 -124
  138. data/lib/concurrent/thread_pool_executor.rb +0 -30
  139. data/spec/concurrent/atomic_spec.rb +0 -201
  140. data/spec/concurrent/count_down_latch_spec.rb +0 -125
  141. data/spec/concurrent/safe_task_executor_spec.rb +0 -58
  142. data/spec/concurrent/simple_actor_ref_spec.rb +0 -219
@@ -1,7 +1,7 @@
1
1
  require 'thread'
2
2
 
3
3
  require 'concurrent/obligation'
4
- require 'concurrent/copy_on_write_observer_set'
4
+ require 'concurrent/observable'
5
5
 
6
6
  module Concurrent
7
7
 
@@ -9,20 +9,21 @@ module Concurrent
9
9
 
10
10
  class IVar
11
11
  include Obligation
12
+ include Concurrent::Observable
12
13
 
13
14
  NO_VALUE = Object.new
14
15
 
15
- # Create a new +Ivar+ in the +:pending+ state with the (optional) initial value.
16
+ # Create a new `Ivar` in the `:pending` state with the (optional) initial value.
16
17
  #
17
18
  # @param [Object] value the initial value
18
19
  # @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
20
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
21
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
22
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
22
23
  # returning the value returned from the proc
23
24
  def initialize(value = NO_VALUE, opts = {})
24
25
  init_obligation
25
- @observers = CopyOnWriteObserverSet.new
26
+ self.observers = CopyOnWriteObserverSet.new
26
27
  set_deref_options(opts)
27
28
 
28
29
  if value == NO_VALUE
@@ -34,26 +35,32 @@ module Concurrent
34
35
 
35
36
  # Add an observer on this object that will receive notification on update.
36
37
  #
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).
38
+ # Upon completion the `IVar` will notify all observers in a thread-say way. The `func`
39
+ # method of the observer will be called with three arguments: the `Time` at which the
40
+ # `Future` completed the asynchronous operation, the final `value` (or `nil` on rejection),
41
+ # and the final `reason` (or `nil` on fulfillment).
41
42
  #
42
43
  # @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)
44
+ # @param [Symbol] func symbol naming the method to call when this `Observable` has changes`
45
+ def add_observer(observer = nil, func = :update, &block)
46
+ raise ArgumentError.new('cannot provide both an observer and a block') if observer && block
45
47
  direct_notification = false
46
48
 
49
+ if block
50
+ observer = block
51
+ func = :call
52
+ end
53
+
47
54
  mutex.synchronize do
48
55
  if event.set?
49
56
  direct_notification = true
50
57
  else
51
- @observers.add_observer(observer, func)
58
+ observers.add_observer(observer, func)
52
59
  end
53
60
  end
54
61
 
55
62
  observer.send(func, Time.now, self.value, reason) if direct_notification
56
- func
63
+ observer
57
64
  end
58
65
 
59
66
  def set(value)
@@ -72,7 +79,7 @@ module Concurrent
72
79
  end
73
80
 
74
81
  time = Time.now
75
- @observers.notify_and_delete_observers{ [time, self.value, reason] }
82
+ observers.notify_and_delete_observers{ [time, self.value, reason] }
76
83
  self
77
84
  end
78
85
  end
@@ -1,5 +1,6 @@
1
1
  require 'concurrent/dereferenceable'
2
- require 'concurrent/event'
2
+ require 'concurrent/atomic/condition'
3
+ require 'concurrent/atomic/event'
3
4
 
4
5
  module Concurrent
5
6
 
@@ -2,7 +2,7 @@ require 'thread'
2
2
  require 'timeout'
3
3
 
4
4
  require 'concurrent/dereferenceable'
5
- require 'concurrent/event'
5
+ require 'concurrent/atomic/event'
6
6
 
7
7
  module Concurrent
8
8
 
@@ -48,11 +48,17 @@ module Concurrent
48
48
  end
49
49
 
50
50
  def state
51
- mutex.synchronize { @state }
51
+ mutex.lock
52
+ result = @state
53
+ mutex.unlock
54
+ result
52
55
  end
53
56
 
54
57
  def reason
55
- mutex.synchronize { @reason }
58
+ mutex.lock
59
+ result = @reason
60
+ mutex.unlock
61
+ result
56
62
  end
57
63
 
58
64
  protected
@@ -0,0 +1,33 @@
1
+ require 'concurrent/atomic/copy_on_notify_observer_set'
2
+ require 'concurrent/atomic/copy_on_write_observer_set'
3
+
4
+ module Concurrent
5
+
6
+ module Observable
7
+
8
+ # @return [Object] the added observer
9
+ def add_observer(*args, &block)
10
+ observers.add_observer(*args, &block)
11
+ end
12
+
13
+ # @return [Object] the deleted observer
14
+ def delete_observer(*args)
15
+ observers.delete_observer(*args)
16
+ end
17
+
18
+ # @return [Observable] self
19
+ def delete_observers
20
+ observers.delete_observers
21
+ self
22
+ end
23
+
24
+ # @return [Integer] the observers count
25
+ def count_observers
26
+ observers.count_observers
27
+ end
28
+
29
+ protected
30
+
31
+ attr_accessor :observers
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ module Concurrent
2
+
3
+ # A mixin module for parsing options hashes related to gem-level configuration.
4
+ module OptionsParser
5
+
6
+ # Get the requested `Executor` based on the values set in the options hash.
7
+ #
8
+ # @param [Hash] opts the options defining the requested executor
9
+ # @option opts [Executor] :executor (`nil`) when set use the given `Executor` instance
10
+ # @option opts [Boolean] :operation (`false`) when true use the global operation pool
11
+ # @option opts [Boolean] :task (`true`) when true use the global task pool
12
+ #
13
+ # @return [Executor] the requested thread pool (default: global task pool)
14
+ def get_executor_from(opts = {})
15
+ if opts[:executor]
16
+ opts[:executor]
17
+ elsif opts[:operation] == true || opts[:task] == false
18
+ Concurrent.configuration.global_operation_pool
19
+ else
20
+ Concurrent.configuration.global_task_pool
21
+ end
22
+ end
23
+
24
+ # Get the requested `Executor` based on the values set in the options hash.
25
+ #
26
+ # @param [Hash] opts the options defining the requested executor
27
+ # @option opts [Executor] :task_executor (`nil`) when set use the given `Executor` instance
28
+ #
29
+ # @return [Executor] the requested thread pool (default: global task pool)
30
+ def get_task_executor_from(opts = {})
31
+ opts[:task_executor] || opts[:executor] || Concurrent.configuration.global_task_pool
32
+ end
33
+
34
+ # Get the requested `Executor` based on the values set in the options hash.
35
+ #
36
+ # @param [Hash] opts the options defining the requested executor
37
+ # @option opts [Executor] :task_executor (`nil`) when set use the given `Executor` instance
38
+ #
39
+ # @return [Executor] the requested thread pool (default: global operation pool)
40
+ def get_operation_executor_from(opts = {})
41
+ opts[:operation_executor] || opts[:executor] || Concurrent.configuration.global_operation_pool
42
+ end
43
+
44
+ extend self
45
+ end
46
+ end
@@ -1,45 +1,43 @@
1
1
  require 'thread'
2
2
 
3
- require 'concurrent/configuration'
4
3
  require 'concurrent/obligation'
4
+ require 'concurrent/options_parser'
5
5
 
6
6
  module Concurrent
7
7
 
8
8
  class Promise
9
9
  include Obligation
10
- include OptionsParser
11
10
 
12
11
  # Initialize a new Promise with the provided options.
13
12
  #
14
- # @param [Object] initial the initial value
15
13
  # @param [Hash] opts the options used to define the behavior at update and deref
16
14
  #
17
- # @option opts [Promise] :parent the parent +Promise+ when building a chain/tree
15
+ # @option opts [Promise] :parent the parent `Promise` when building a chain/tree
18
16
  # @option opts [Proc] :on_fulfill fulfillment handler
19
17
  # @option opts [Proc] :on_reject rejection handler
20
18
  #
21
- # @option opts [Boolean] :operation (false) when +true+ will execute the future on the global
22
- # operation pool (for long-running operations), when +false+ will execute the future on the
19
+ # @option opts [Boolean] :operation (false) when `true` will execute the future on the global
20
+ # operation pool (for long-running operations), when `false` will execute the future on the
23
21
  # global task pool (for short-running tasks)
24
22
  # @option opts [object] :executor when provided will run all operations on
25
23
  # this executor rather than the global thread pool (overrides :operation)
26
24
  #
27
- # @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
28
- # @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
29
- # @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
25
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
26
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
27
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
30
28
  # returning the value returned from the proc
31
29
  #
32
30
  # @see http://wiki.commonjs.org/wiki/Promises/A
33
31
  # @see http://promises-aplus.github.io/promises-spec/
34
32
  def initialize(opts = {}, &block)
35
- opts.delete_if {|k, v| v.nil?}
33
+ opts.delete_if { |k, v| v.nil? }
36
34
 
37
- @executor = get_executor_from(opts)
35
+ @executor = OptionsParser::get_executor_from(opts)
38
36
  @parent = opts.fetch(:parent) { nil }
39
- @on_fulfill = opts.fetch(:on_fulfill) { Proc.new{ |result| result } }
40
- @on_reject = opts.fetch(:on_reject) { Proc.new{ |reason| raise reason } }
37
+ @on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
38
+ @on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
41
39
 
42
- @promise_body = block || Proc.new{|result| result }
40
+ @promise_body = block || Proc.new { |result| result }
43
41
  @state = :unscheduled
44
42
  @children = []
45
43
 
@@ -48,13 +46,13 @@ module Concurrent
48
46
 
49
47
  # @return [Promise]
50
48
  def self.fulfill(value, opts = {})
51
- Promise.new(opts).tap{ |p| p.send(:synchronized_set_state!, true, value, nil) }
49
+ Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, true, value, nil) }
52
50
  end
53
51
 
54
52
 
55
53
  # @return [Promise]
56
54
  def self.reject(reason, opts = {})
57
- Promise.new(opts).tap{ |p| p.send(:synchronized_set_state!, false, nil, reason) }
55
+ Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, false, nil, reason) }
58
56
  end
59
57
 
60
58
  # @return [Promise]
@@ -79,7 +77,7 @@ module Concurrent
79
77
  # @return [Promise] the new promise
80
78
  def then(rescuer = nil, &block)
81
79
  raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
82
- block = Proc.new{ |result| result } if block.nil?
80
+ block = Proc.new { |result| result } if block.nil?
83
81
  child = Promise.new(
84
82
  parent: self,
85
83
  executor: @executor,
@@ -107,6 +105,7 @@ module Concurrent
107
105
  def rescue(&block)
108
106
  self.then(block)
109
107
  end
108
+
110
109
  alias_method :catch, :rescue
111
110
  alias_method :on_error, :rescue
112
111
 
@@ -126,13 +125,13 @@ module Concurrent
126
125
 
127
126
  # @!visibility private
128
127
  def on_fulfill(result)
129
- realize Proc.new{ @on_fulfill.call(result) }
128
+ realize Proc.new { @on_fulfill.call(result) }
130
129
  nil
131
130
  end
132
131
 
133
132
  # @!visibility private
134
133
  def on_reject(reason)
135
- realize Proc.new{ @on_reject.call(reason) }
134
+ realize Proc.new { @on_reject.call(reason) }
136
135
  nil
137
136
  end
138
137
 
@@ -144,14 +143,14 @@ module Concurrent
144
143
  # @!visibility private
145
144
  def realize(task)
146
145
  @executor.post do
147
- success, value, reason = SafeTaskExecutor.new( task ).execute
146
+ success, value, reason = SafeTaskExecutor.new(task).execute
148
147
 
149
148
  children_to_notify = mutex.synchronize do
150
149
  set_state!(success, value, reason)
151
150
  @children.dup
152
151
  end
153
152
 
154
- children_to_notify.each{ |child| notify_child(child) }
153
+ children_to_notify.each { |child| notify_child(child) }
155
154
  end
156
155
  end
157
156
 
@@ -161,9 +160,9 @@ module Concurrent
161
160
  end
162
161
 
163
162
  def synchronized_set_state!(success, value, reason)
164
- mutex.synchronize do
165
- set_state!(success, value, reason)
166
- end
163
+ mutex.lock
164
+ set_state!(success, value, reason)
165
+ mutex.unlock
167
166
  end
168
167
  end
169
168
  end
@@ -1,41 +1,37 @@
1
- require 'observer'
2
- require 'concurrent/obligation'
3
- require 'concurrent/safe_task_executor'
1
+ require 'concurrent/ivar'
2
+ require 'concurrent/utility/timer'
3
+ require 'concurrent/executor/safe_task_executor'
4
4
 
5
5
  module Concurrent
6
6
 
7
- class ScheduledTask
8
- include Obligation
9
-
10
- SchedulingError = Class.new(ArgumentError)
7
+ class ScheduledTask < IVar
11
8
 
12
9
  attr_reader :schedule_time
13
10
 
14
- def initialize(schedule_time, opts = {}, &block)
15
- raise SchedulingError.new('no block given') unless block_given?
16
- calculate_schedule_time!(schedule_time) # raise exception if in past
11
+ def initialize(intended_time, opts = {}, &block)
12
+ raise ArgumentError.new('no block given') unless block_given?
13
+ TimerSet.calculate_schedule_time(intended_time) # raises exceptons
14
+
15
+ super(NO_VALUE, opts)
17
16
 
18
- init_obligation
19
- @observers = CopyOnWriteObserverSet.new
17
+ self.observers = CopyOnNotifyObserverSet.new
18
+ @intended_time = intended_time
20
19
  @state = :unscheduled
21
- @intended_schedule_time = schedule_time
22
- @schedule_time = nil
23
20
  @task = block
24
- set_deref_options(opts)
25
21
  end
26
22
 
27
23
  # @since 0.5.0
28
24
  def execute
29
25
  if compare_and_set_state(:pending, :unscheduled)
30
- @schedule_time = calculate_schedule_time!(@intended_schedule_time).freeze
31
- Thread.new { work }
26
+ @schedule_time = TimerSet.calculate_schedule_time(@intended_time)
27
+ Concurrent::timer(@schedule_time.to_f - Time.now.to_f, &method(:process_task))
32
28
  self
33
29
  end
34
30
  end
35
31
 
36
32
  # @since 0.5.0
37
- def self.execute(schedule_time, opts = {}, &block)
38
- return ScheduledTask.new(schedule_time, opts, &block).execute
33
+ def self.execute(intended_time, opts = {}, &block)
34
+ return ScheduledTask.new(intended_time, opts, &block).execute
39
35
  end
40
36
 
41
37
  def cancelled?
@@ -53,20 +49,19 @@ module Concurrent
53
49
  true
54
50
  end
55
51
  end
56
-
57
52
  alias_method :stop, :cancel
58
53
 
59
- def add_observer(observer, func = :update)
54
+ def add_observer(*args, &block)
60
55
  if_state(:unscheduled, :pending, :in_progress) do
61
- @observers.add_observer(observer, func)
56
+ observers.add_observer(*args, &block)
62
57
  end
63
58
  end
64
59
 
65
- protected
60
+ protected :set, :fail, :complete
66
61
 
67
- def work
68
- sleep_until_scheduled_time
62
+ private
69
63
 
64
+ def process_task
70
65
  if compare_and_set_state(:in_progress, :pending)
71
66
  success, val, reason = SafeTaskExecutor.new(@task).execute
72
67
 
@@ -76,26 +71,7 @@ module Concurrent
76
71
  end
77
72
 
78
73
  time = Time.now
79
- @observers.notify_and_delete_observers{ [time, self.value, reason] }
80
- end
81
-
82
- end
83
-
84
- private
85
-
86
- def sleep_until_scheduled_time
87
- while (diff = @schedule_time.to_f - Time.now.to_f) > 0
88
- sleep(diff > 60 ? 60 : diff)
89
- end
90
- end
91
-
92
- def calculate_schedule_time!(schedule_time, now = Time.now)
93
- if schedule_time.is_a?(Time)
94
- raise SchedulingError.new('schedule time must be in the future') if schedule_time <= now
95
- schedule_time.dup
96
- else
97
- raise SchedulingError.new('seconds must be greater than zero') if schedule_time.to_f <= 0.0
98
- now + schedule_time.to_f
74
+ observers.notify_and_delete_observers{ [time, self.value, reason] }
99
75
  end
100
76
  end
101
77
  end
@@ -1,10 +1,11 @@
1
- require 'thread'
2
- require 'observer'
3
-
4
1
  require 'concurrent/dereferenceable'
2
+ require 'concurrent/observable'
3
+ require 'concurrent/atomic/atomic_boolean'
4
+ require 'concurrent/executor/executor'
5
+ require 'concurrent/executor/safe_task_executor'
6
+
7
+ # deprecated Updated to use `Executor` instead of `Runnable`
5
8
  require 'concurrent/runnable'
6
- require 'concurrent/stoppable'
7
- require 'concurrent/utilities'
8
9
 
9
10
  module Concurrent
10
11
 
@@ -15,35 +16,35 @@ module Concurrent
15
16
  # task itself is tightly coupled with the concurrency logic. Second, an exception in
16
17
  # raised while performing the task can cause the entire thread to abend. In a
17
18
  # long-running application where the task thread is intended to run for days/weeks/years
18
- # a crashed task thread can pose a significant problem. +TimerTask+ alleviates both problems.
19
+ # a crashed task thread can pose a significant problem. `TimerTask` alleviates both problems.
19
20
  #
20
- # When a +TimerTask+ is launched it starts a thread for monitoring the execution interval.
21
- # The +TimerTask+ thread does not perform the task, however. Instead, the TimerTask
21
+ # When a `TimerTask` is launched it starts a thread for monitoring the execution interval.
22
+ # The `TimerTask` thread does not perform the task, however. Instead, the TimerTask
22
23
  # launches the task on a separate thread. Should the task experience an unrecoverable
23
- # crash only the task thread will crash. This makes the +TimerTask+ very fault tolerant
24
- # Additionally, the +TimerTask+ thread can respond to the success or failure of the task,
25
- # performing logging or ancillary operations. +TimerTask+ can also be configured with a
24
+ # crash only the task thread will crash. This makes the `TimerTask` very fault tolerant
25
+ # Additionally, the `TimerTask` thread can respond to the success or failure of the task,
26
+ # performing logging or ancillary operations. `TimerTask` can also be configured with a
26
27
  # timeout value allowing it to kill a task that runs too long.
27
28
  #
28
- # One other advantage of +TimerTask+ is it forces the business logic to be completely decoupled
29
+ # One other advantage of `TimerTask` is it forces the business logic to be completely decoupled
29
30
  # from the concurrency logic. The business logic can be tested separately then passed to the
30
- # +TimerTask+ for scheduling and running.
31
+ # `TimerTask` for scheduling and running.
31
32
  #
32
- # In some cases it may be necessary for a +TimerTask+ to affect its own execution cycle.
33
+ # In some cases it may be necessary for a `TimerTask` to affect its own execution cycle.
33
34
  # To facilitate this a reference to the task object is passed into the block as a block
34
35
  # argument every time the task is executed.
35
36
  #
36
- # The +TimerTask+ class includes the +Dereferenceable+ mixin module so the result of
37
- # the last execution is always available via the +#value+ method. Derefencing options
38
- # can be passed to the +TimerTask+ during construction or at any later time using the
39
- # +#set_deref_options+ method.
37
+ # The `TimerTask` class includes the `Dereferenceable` mixin module so the result of
38
+ # the last execution is always available via the `#value` method. Derefencing options
39
+ # can be passed to the `TimerTask` during construction or at any later time using the
40
+ # `#set_deref_options` method.
40
41
  #
41
- # +TimerTask+ supports notification through the Ruby standard library
42
+ # `TimerTask` supports notification through the Ruby standard library
42
43
  # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html Observable}
43
- # module. On execution the +TimerTask+ will notify the observers
44
+ # module. On execution the `TimerTask` will notify the observers
44
45
  # with threes arguments: time of execution, the result of the block (or nil on failure),
45
46
  # and any raised exceptions (or nil on success). If the timeout interval is exceeded
46
- # the observer will receive a +Concurrent::TimeoutError+ object as the third argument.
47
+ # the observer will receive a `Concurrent::TimeoutError` object as the third argument.
47
48
  #
48
49
  # @example Basic usage
49
50
  # task = Concurrent::TimerTask.new{ puts 'Boom!' }
@@ -57,7 +58,7 @@ module Concurrent
57
58
  #
58
59
  # task.stop #=> true
59
60
  #
60
- # @example Configuring +:execution_interval+ and +:timeout_interval+
61
+ # @example Configuring `:execution_interval` and `:timeout_interval`
61
62
  # task = Concurrent::TimerTask.new(execution_interval: 5, timeout_interval: 5) do
62
63
  # puts 'Boom!'
63
64
  # end
@@ -65,13 +66,13 @@ module Concurrent
65
66
  # task.execution_interval #=> 5
66
67
  # task.timeout_interval #=> 5
67
68
  #
68
- # @example Immediate execution with +:run_now+
69
+ # @example Immediate execution with `:run_now`
69
70
  # task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' }
70
71
  # task.run!
71
72
  #
72
73
  # #=> 'Boom!'
73
74
  #
74
- # @example Last +#value+ and +Dereferenceable+ mixin
75
+ # @example Last `#value` and `Dereferenceable` mixin
75
76
  # task = Concurrent::TimerTask.new(
76
77
  # dup_on_deref: true,
77
78
  # execution_interval: 5
@@ -145,151 +146,228 @@ module Concurrent
145
146
  # @see http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html
146
147
  class TimerTask
147
148
  include Dereferenceable
148
- include Runnable
149
- include Stoppable
149
+ include Executor
150
+ include Concurrent::Observable
150
151
 
151
- # Default +:execution_interval+
152
+ # Default `:execution_interval` in seconds.
152
153
  EXECUTION_INTERVAL = 60
153
154
 
154
- # Default +:timeout_interval+
155
+ # Default `:timeout_interval` in seconds.
155
156
  TIMEOUT_INTERVAL = 30
156
157
 
157
- # Number of seconds after the task completes before the task is
158
- # performed again.
159
- attr_reader :execution_interval
160
-
161
- # Number of seconds the task can run before it is considered to have failed.
162
- # Failed tasks are forcibly killed.
163
- attr_reader :timeout_interval
164
-
165
158
  # Create a new TimerTask with the given task and configuration.
166
159
  #
167
- # @param [Hash] opts the options defining task execution.
168
- # @option opts [Integer] :execution_interval number of seconds between
169
- # task executions (default: EXECUTION_INTERVAL)
170
- # @option opts [Integer] :timeout_interval number of seconds a task can
171
- # run before it is considered to have failed (default: TIMEOUT_INTERVAL)
172
- # @option opts [Boolean] :run_now Whether to run the task immediately
173
- # upon instantiation or to wait until the first #execution_interval
174
- # has passed (default: false)
175
- #
176
- # @raise ArgumentError when no block is given.
177
- #
178
- # @yield to the block after :execution_interval seconds have passed since
179
- # the last yield
180
- # @yieldparam task a reference to the +TimerTask+ instance so that the
181
- # block can control its own lifecycle. Necessary since +self+ will
182
- # refer to the execution context of the block rather than the running
183
- # +TimerTask+.
160
+ # @!macro [attach] timer_task_initialize
161
+ # @param [Hash] opts the options defining task execution.
162
+ # @option opts [Integer] :execution_interval number of seconds between
163
+ # task executions (default: EXECUTION_INTERVAL)
164
+ # @option opts [Integer] :timeout_interval number of seconds a task can
165
+ # run before it is considered to have failed (default: TIMEOUT_INTERVAL)
166
+ # @option opts [Boolean] :run_now Whether to run the task immediately
167
+ # upon instantiation or to wait until the first # execution_interval
168
+ # has passed (default: false)
169
+ #
170
+ # @raise ArgumentError when no block is given.
171
+ #
172
+ # @yield to the block after :execution_interval seconds have passed since
173
+ # the last yield
174
+ # @yieldparam task a reference to the `TimerTask` instance so that the
175
+ # block can control its own lifecycle. Necessary since `self` will
176
+ # refer to the execution context of the block rather than the running
177
+ # `TimerTask`.
178
+ #
179
+ # @note Calls Concurrent::Dereferenceable# set_deref_options passing `opts`.
180
+ # All options supported by Concurrent::Dereferenceable can be set
181
+ # during object initialization.
184
182
  #
185
- # @note Calls Concurrent::Dereferenceable#set_deref_options passing +opts+.
186
- # All options supported by Concurrent::Dereferenceable can be set
187
- # during object initialization.
188
- #
189
- # @see Concurrent::Dereferenceable#set_deref_options
190
- def initialize(opts = {}, &block)
183
+ # @return [TimerTask] the new `TimerTask`
184
+ #
185
+ # @see Concurrent::Dereferenceable# set_deref_options
186
+ def initialize(opts = {}, &task)
191
187
  raise ArgumentError.new('no block given') unless block_given?
192
188
 
189
+ init_executor
190
+ set_deref_options(opts)
191
+
193
192
  self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
194
193
  self.timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
195
194
  @run_now = opts[:now] || opts[:run_now]
195
+ @executor = Concurrent::SafeTaskExecutor.new(task)
196
+ @running = Concurrent::AtomicBoolean.new(false)
196
197
 
197
- @task = block
198
- @observers = CopyOnWriteObserverSet.new
199
- init_mutex
200
- set_deref_options(opts)
198
+ self.observers = CopyOnNotifyObserverSet.new
201
199
  end
202
200
 
203
- # Number of seconds after the task completes before the task is
204
- # performed again.
201
+ # Is the executor running?
205
202
  #
206
- # @param [Float] value number of seconds
203
+ # @return [Boolean] `true` when running, `false` when shutting down or shutdown
204
+ def running?
205
+ @running.true?
206
+ end
207
+
208
+ # Execute a previously created `TimerTask`.
207
209
  #
208
- # @raise ArgumentError when value is non-numeric or not greater than zero
209
- def execution_interval=(value)
210
- if (value = value.to_f) <= 0.0
211
- raise ArgumentError.new("'execution_interval' must be non-negative number")
210
+ # @return [TimerTask] a reference to `self`
211
+ #
212
+ # @example Instance and execute in separate steps
213
+ # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }
214
+ # task.running? #=> false
215
+ # task.execute
216
+ # task.running? #=> true
217
+ #
218
+ # @example Instance and execute in one line
219
+ # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }.execute
220
+ # task.running? #=> true
221
+ #
222
+ # @since 0.6.0
223
+ def execute
224
+ mutex.synchronize do
225
+ if @running.false?
226
+ @running.make_true
227
+ schedule_next_task(@run_now ? 0 : @execution_interval)
228
+ end
212
229
  end
213
- @execution_interval = value
230
+ self
214
231
  end
215
232
 
216
- # Number of seconds the task can run before it is considered to have failed.
217
- # Failed tasks are forcibly killed.
233
+ # Create and execute a new `TimerTask`.
218
234
  #
219
- # @param [Float] value number of seconds
235
+ # @!macro timer_task_initialize
220
236
  #
221
- # @raise ArgumentError when value is non-numeric or not greater than zero
222
- def timeout_interval=(value)
237
+ # @example
238
+ # task = Concurrent::TimerTask.execute(execution_interval: 10){ print "Hello World\n" }
239
+ # task.running? #=> true
240
+ #
241
+ # @since 0.6.0
242
+ def self.execute(opts = {}, &task)
243
+ TimerTask.new(opts, &task).execute
244
+ end
245
+
246
+ # @!attribute [rw] execution_interval
247
+ # @return [Fixnum] Number of seconds after the task completes before the
248
+ # task is performed again.
249
+ def execution_interval
250
+ mutex.lock
251
+ result = @execution_interval
252
+ mutex.unlock
253
+ result
254
+ end
255
+
256
+ # @!attribute [rw] execution_interval
257
+ # @return [Fixnum] Number of seconds after the task completes before the
258
+ # task is performed again.
259
+ def execution_interval=(value)
223
260
  if (value = value.to_f) <= 0.0
224
- raise ArgumentError.new("'timeout_interval' must be non-negative number")
261
+ raise ArgumentError.new('must be greater than zero')
262
+ else
263
+ mutex.lock
264
+ result = @execution_interval = value
265
+ mutex.unlock
266
+ result
225
267
  end
226
- @timeout_interval = value
227
268
  end
228
269
 
229
- def add_observer(observer, func = :update)
230
- @observers.add_observer(observer, func)
270
+ # @!attribute [rw] timeout_interval
271
+ # @return [Fixnum] Number of seconds the task can run before it is
272
+ # considered to have failed.
273
+ def timeout_interval
274
+ mutex.lock
275
+ result = @timeout_interval
276
+ mutex.unlock
277
+ result
231
278
  end
232
279
 
233
- # Terminate with extreme prejudice. Useful in cases where +#stop+ doesn't
234
- # work because one of the threads becomes unresponsive.
235
- #
236
- # @return [Boolean] indicating whether or not the +TimerTask+ was killed
237
- #
238
- # @note Do not use this method unless +#stop+ has failed.
239
- def kill
240
- return true unless running?
241
- mutex.synchronize do
242
- @running = false
243
- Thread.kill(@worker) unless @worker.nil?
244
- Thread.kill(@monitor) unless @monitor.nil?
280
+ # @!attribute [rw] timeout_interval
281
+ # @return [Fixnum] Number of seconds the task can run before it is
282
+ # considered to have failed.
283
+ def timeout_interval=(value)
284
+ if (value = value.to_f) <= 0.0
285
+ raise ArgumentError.new('must be greater than zero')
286
+ else
287
+ mutex.lock
288
+ result = @timeout_interval = value
289
+ mutex.unlock
290
+ result
245
291
  end
246
- return true
247
- rescue
248
- return false
249
- ensure
250
- @worker = @monitor = nil
251
292
  end
252
- alias_method :terminate, :kill
253
293
 
254
- alias_method :cancel, :stop
294
+ # @deprecated Updated to use `Executor` instead of `Runnable`
295
+ def terminate(*args) deprecated(:terminate, :kill, *args); end
296
+
297
+ # @deprecated Updated to use `Executor` instead of `Runnable`
298
+ def stop(*args) deprecated(:stop, :shutdown, *args); end
299
+
300
+ # @deprecated Updated to use `Executor` instead of `Runnable`
301
+ def cancel(*args) deprecated(:cancel, :shutdown, *args); end
302
+
303
+ # @deprecated Updated to use `Executor` instead of `Runnable`
304
+ def run!(*args) deprecated(:run!, :execute); end
305
+
306
+ # @deprecated Updated to use `Executor` instead of `Runnable`
307
+ def self.run!(*args, &block)
308
+ warn "[DEPRECATED] `run!` is deprecated, please use `execute` instead."
309
+ Concurrent::Runnable::Context.new(TimerTask.new(*args, &block))
310
+ end
311
+
312
+ # @deprecated Updated to use `Executor` instead of `Runnable`
313
+ def run
314
+ raise Concurrent::Runnable::LifecycleError.new('already running') if @running.true?
315
+ self.execute
316
+ self.wait_for_termination
317
+ true
318
+ end
319
+
320
+ private :post, :<<
255
321
 
256
322
  protected
257
323
 
258
- def on_run # :nodoc:
259
- @monitor = Thread.current
324
+ # @!visibility private
325
+ def shutdown_execution
326
+ @running.make_false
327
+ super
260
328
  end
261
329
 
262
- def on_stop # :nodoc:
263
- before_stop_proc.call if before_stop_proc
264
- @monitor.wakeup if @monitor.alive?
265
- Thread.pass
330
+ # @!visibility private
331
+ def kill_execution
332
+ @running.make_false
333
+ super
266
334
  end
267
335
 
268
- def on_task # :nodoc:
269
- if @run_now
270
- @run_now = false
271
- else
272
- sleep(@execution_interval)
273
- end
274
- execute_task
336
+ # @!visibility private
337
+ def schedule_next_task(interval = execution_interval)
338
+ Concurrent::timer(interval, Concurrent::Event.new, &method(:execute_task))
275
339
  end
276
340
 
277
- def execute_task # :nodoc:
278
- @value = ex = nil
279
- @worker = Thread.new do
280
- Thread.current.abort_on_exception = false
281
- Thread.current[:result] = @task.call(self)
341
+ # @!visibility private
342
+ def execute_task(completion)
343
+ return unless @running.true?
344
+ Concurrent::timer(timeout_interval, completion, &method(:timeout_task))
345
+ success, value, reason = @executor.execute(self)
346
+ if completion.try?
347
+ self.value = value
348
+ schedule_next_task
349
+ time = Time.now
350
+ observers.notify_observers do
351
+ [time, self.value, reason]
352
+ end
282
353
  end
283
- raise TimeoutError if @worker.join(@timeout_interval).nil?
284
- mutex.synchronize { @value = @worker[:result] }
285
- rescue Exception => e
286
- ex = e
287
- ensure
288
- @observers.notify_observers(Time.now, self.value, ex)
289
- unless @worker.nil?
290
- Thread.kill(@worker)
291
- @worker = nil
354
+ end
355
+
356
+ # @!visibility private
357
+ def timeout_task(completion)
358
+ return unless @running.true?
359
+ if completion.try?
360
+ self.value = value
361
+ schedule_next_task
362
+ observers.notify_observers(Time.now, nil, Concurrent::TimeoutError.new)
292
363
  end
293
364
  end
365
+
366
+ # @deprecated Updated to use `Executor` instead of `Runnable`
367
+ # @!visibility private
368
+ def deprecated(old, new, *args)
369
+ warn "[DEPRECATED] `#{old}` is deprecated, please use `#{new}` instead."
370
+ self.send(new, *args)
371
+ end
294
372
  end
295
373
  end