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

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