concurrent-ruby 0.7.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/LICENSE.txt +21 -0
  2. data/README.md +217 -0
  3. data/lib/concurrent.rb +45 -0
  4. data/lib/concurrent/actor.rb +104 -0
  5. data/lib/concurrent/actor/behaviour.rb +70 -0
  6. data/lib/concurrent/actor/behaviour/abstract.rb +48 -0
  7. data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
  8. data/lib/concurrent/actor/behaviour/buffer.rb +54 -0
  9. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
  10. data/lib/concurrent/actor/behaviour/executes_context.rb +18 -0
  11. data/lib/concurrent/actor/behaviour/linking.rb +42 -0
  12. data/lib/concurrent/actor/behaviour/pausing.rb +77 -0
  13. data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
  14. data/lib/concurrent/actor/behaviour/sets_results.rb +36 -0
  15. data/lib/concurrent/actor/behaviour/supervised.rb +58 -0
  16. data/lib/concurrent/actor/behaviour/supervising.rb +34 -0
  17. data/lib/concurrent/actor/behaviour/terminates_children.rb +13 -0
  18. data/lib/concurrent/actor/behaviour/termination.rb +54 -0
  19. data/lib/concurrent/actor/context.rb +153 -0
  20. data/lib/concurrent/actor/core.rb +213 -0
  21. data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
  22. data/lib/concurrent/actor/envelope.rb +41 -0
  23. data/lib/concurrent/actor/errors.rb +27 -0
  24. data/lib/concurrent/actor/internal_delegations.rb +49 -0
  25. data/lib/concurrent/actor/public_delegations.rb +40 -0
  26. data/lib/concurrent/actor/reference.rb +81 -0
  27. data/lib/concurrent/actor/root.rb +37 -0
  28. data/lib/concurrent/actor/type_check.rb +48 -0
  29. data/lib/concurrent/actor/utils.rb +10 -0
  30. data/lib/concurrent/actor/utils/ad_hoc.rb +21 -0
  31. data/lib/concurrent/actor/utils/balancer.rb +40 -0
  32. data/lib/concurrent/actor/utils/broadcast.rb +52 -0
  33. data/lib/concurrent/actor/utils/pool.rb +59 -0
  34. data/lib/concurrent/actress.rb +3 -0
  35. data/lib/concurrent/agent.rb +230 -0
  36. data/lib/concurrent/async.rb +284 -0
  37. data/lib/concurrent/atomic.rb +91 -0
  38. data/lib/concurrent/atomic/atomic_boolean.rb +202 -0
  39. data/lib/concurrent/atomic/atomic_fixnum.rb +203 -0
  40. data/lib/concurrent/atomic/condition.rb +67 -0
  41. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +118 -0
  42. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +117 -0
  43. data/lib/concurrent/atomic/count_down_latch.rb +116 -0
  44. data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
  45. data/lib/concurrent/atomic/event.rb +98 -0
  46. data/lib/concurrent/atomic/synchronization.rb +51 -0
  47. data/lib/concurrent/atomic/thread_local_var.rb +82 -0
  48. data/lib/concurrent/atomic_reference/concurrent_update_error.rb +8 -0
  49. data/lib/concurrent/atomic_reference/direct_update.rb +50 -0
  50. data/lib/concurrent/atomic_reference/jruby.rb +14 -0
  51. data/lib/concurrent/atomic_reference/mutex_atomic.rb +77 -0
  52. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +25 -0
  53. data/lib/concurrent/atomic_reference/rbx.rb +19 -0
  54. data/lib/concurrent/atomic_reference/ruby.rb +37 -0
  55. data/lib/concurrent/atomics.rb +11 -0
  56. data/lib/concurrent/channel/buffered_channel.rb +85 -0
  57. data/lib/concurrent/channel/channel.rb +41 -0
  58. data/lib/concurrent/channel/unbuffered_channel.rb +35 -0
  59. data/lib/concurrent/channel/waitable_list.rb +40 -0
  60. data/lib/concurrent/channels.rb +5 -0
  61. data/lib/concurrent/collection/blocking_ring_buffer.rb +71 -0
  62. data/lib/concurrent/collection/priority_queue.rb +305 -0
  63. data/lib/concurrent/collection/ring_buffer.rb +59 -0
  64. data/lib/concurrent/collections.rb +3 -0
  65. data/lib/concurrent/configuration.rb +161 -0
  66. data/lib/concurrent/dataflow.rb +108 -0
  67. data/lib/concurrent/delay.rb +104 -0
  68. data/lib/concurrent/dereferenceable.rb +101 -0
  69. data/lib/concurrent/errors.rb +30 -0
  70. data/lib/concurrent/exchanger.rb +34 -0
  71. data/lib/concurrent/executor/cached_thread_pool.rb +44 -0
  72. data/lib/concurrent/executor/executor.rb +282 -0
  73. data/lib/concurrent/executor/fixed_thread_pool.rb +33 -0
  74. data/lib/concurrent/executor/immediate_executor.rb +65 -0
  75. data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
  76. data/lib/concurrent/executor/java_fixed_thread_pool.rb +41 -0
  77. data/lib/concurrent/executor/java_single_thread_executor.rb +22 -0
  78. data/lib/concurrent/executor/java_thread_pool_executor.rb +180 -0
  79. data/lib/concurrent/executor/per_thread_executor.rb +100 -0
  80. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
  81. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +32 -0
  82. data/lib/concurrent/executor/ruby_single_thread_executor.rb +74 -0
  83. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +288 -0
  84. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +72 -0
  85. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  86. data/lib/concurrent/executor/serialized_execution.rb +126 -0
  87. data/lib/concurrent/executor/single_thread_executor.rb +35 -0
  88. data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
  89. data/lib/concurrent/executor/timer_set.rb +143 -0
  90. data/lib/concurrent/executors.rb +9 -0
  91. data/lib/concurrent/future.rb +125 -0
  92. data/lib/concurrent/ivar.rb +111 -0
  93. data/lib/concurrent/lazy_register.rb +58 -0
  94. data/lib/concurrent/logging.rb +17 -0
  95. data/lib/concurrent/mvar.rb +200 -0
  96. data/lib/concurrent/obligation.rb +171 -0
  97. data/lib/concurrent/observable.rb +40 -0
  98. data/lib/concurrent/options_parser.rb +48 -0
  99. data/lib/concurrent/promise.rb +170 -0
  100. data/lib/concurrent/scheduled_task.rb +79 -0
  101. data/lib/concurrent/timer_task.rb +341 -0
  102. data/lib/concurrent/tvar.rb +248 -0
  103. data/lib/concurrent/utilities.rb +3 -0
  104. data/lib/concurrent/utility/processor_count.rb +152 -0
  105. data/lib/concurrent/utility/timeout.rb +35 -0
  106. data/lib/concurrent/utility/timer.rb +21 -0
  107. data/lib/concurrent/version.rb +3 -0
  108. data/lib/concurrent_ruby.rb +1 -0
  109. data/lib/concurrent_ruby_ext.jar +0 -0
  110. data/lib/concurrent_ruby_ext.so +0 -0
  111. data/lib/extension_helper.rb +28 -0
  112. metadata +163 -0
@@ -0,0 +1,40 @@
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
+ # as #add_observer but it can be used for chaining
14
+ # @return [Observable] self
15
+ def with_observer(*args, &block)
16
+ add_observer *args, &block
17
+ self
18
+ end
19
+
20
+ # @return [Object] the deleted observer
21
+ def delete_observer(*args)
22
+ observers.delete_observer(*args)
23
+ end
24
+
25
+ # @return [Observable] self
26
+ def delete_observers
27
+ observers.delete_observers
28
+ self
29
+ end
30
+
31
+ # @return [Integer] the observers count
32
+ def count_observers
33
+ observers.count_observers
34
+ end
35
+
36
+ protected
37
+
38
+ attr_accessor :observers
39
+ end
40
+ end
@@ -0,0 +1,48 @@
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, nil] the requested thread pool, or nil when no option specified
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
+ elsif opts[:operation] == false || opts[:task] == true
20
+ Concurrent.configuration.global_task_pool
21
+ else
22
+ nil
23
+ end
24
+ end
25
+
26
+ # Get the requested `Executor` based on the values set in the options hash.
27
+ #
28
+ # @param [Hash] opts the options defining the requested executor
29
+ # @option opts [Executor] :task_executor (`nil`) when set use the given `Executor` instance
30
+ #
31
+ # @return [Executor] the requested thread pool (default: global task pool)
32
+ def get_task_executor_from(opts = {})
33
+ opts[:task_executor] || opts[:executor] || Concurrent.configuration.global_task_pool
34
+ end
35
+
36
+ # Get the requested `Executor` based on the values set in the options hash.
37
+ #
38
+ # @param [Hash] opts the options defining the requested executor
39
+ # @option opts [Executor] :task_executor (`nil`) when set use the given `Executor` instance
40
+ #
41
+ # @return [Executor] the requested thread pool (default: global operation pool)
42
+ def get_operation_executor_from(opts = {})
43
+ opts[:operation_executor] || opts[:executor] || Concurrent.configuration.global_operation_pool
44
+ end
45
+
46
+ extend self
47
+ end
48
+ end
@@ -0,0 +1,170 @@
1
+ require 'thread'
2
+
3
+ require 'concurrent/obligation'
4
+ require 'concurrent/options_parser'
5
+
6
+ module Concurrent
7
+
8
+ # TODO unify promise and future to single class, with dataflow
9
+ class Promise
10
+ include Obligation
11
+
12
+ # Initialize a new Promise with the provided options.
13
+ #
14
+ # @param [Hash] opts the options used to define the behavior at update and deref
15
+ #
16
+ # @option opts [Promise] :parent the parent `Promise` when building a chain/tree
17
+ # @option opts [Proc] :on_fulfill fulfillment handler
18
+ # @option opts [Proc] :on_reject rejection handler
19
+ #
20
+ # @option opts [Boolean] :operation (false) when `true` will execute the future on the global
21
+ # operation pool (for long-running operations), when `false` will execute the future on the
22
+ # global task pool (for short-running tasks)
23
+ # @option opts [object] :executor when provided will run all operations on
24
+ # this executor rather than the global thread pool (overrides :operation)
25
+ #
26
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
27
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
28
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
29
+ # returning the value returned from the proc
30
+ #
31
+ # @see http://wiki.commonjs.org/wiki/Promises/A
32
+ # @see http://promises-aplus.github.io/promises-spec/
33
+ def initialize(opts = {}, &block)
34
+ opts.delete_if { |k, v| v.nil? }
35
+
36
+ @executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
37
+ @parent = opts.fetch(:parent) { nil }
38
+ @on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
39
+ @on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
40
+
41
+ @promise_body = block || Proc.new { |result| result }
42
+ @state = :unscheduled
43
+ @children = []
44
+
45
+ init_obligation
46
+ end
47
+
48
+ # @return [Promise]
49
+ def self.fulfill(value, opts = {})
50
+ Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, true, value, nil) }
51
+ end
52
+
53
+
54
+ # @return [Promise]
55
+ def self.reject(reason, opts = {})
56
+ Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, false, nil, reason) }
57
+ end
58
+
59
+ # @return [Promise]
60
+ # @since 0.5.0
61
+ def execute
62
+ if root?
63
+ if compare_and_set_state(:pending, :unscheduled)
64
+ set_pending
65
+ realize(@promise_body)
66
+ end
67
+ else
68
+ @parent.execute
69
+ end
70
+ self
71
+ end
72
+
73
+ # @since 0.5.0
74
+ def self.execute(opts = {}, &block)
75
+ new(opts, &block).execute
76
+ end
77
+
78
+ # @return [Promise] the new promise
79
+ def then(rescuer = nil, &block)
80
+ raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
81
+ block = Proc.new { |result| result } if block.nil?
82
+ child = Promise.new(
83
+ parent: self,
84
+ executor: @executor,
85
+ on_fulfill: block,
86
+ on_reject: rescuer
87
+ )
88
+
89
+ mutex.synchronize do
90
+ child.state = :pending if @state == :pending
91
+ child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled
92
+ child.on_reject(@reason) if @state == :rejected
93
+ @children << child
94
+ end
95
+
96
+ child
97
+ end
98
+
99
+ # @return [Promise]
100
+ def on_success(&block)
101
+ raise ArgumentError.new('no block given') unless block_given?
102
+ self.then &block
103
+ end
104
+
105
+ # @return [Promise]
106
+ def rescue(&block)
107
+ self.then(block)
108
+ end
109
+
110
+ alias_method :catch, :rescue
111
+ alias_method :on_error, :rescue
112
+
113
+ protected
114
+
115
+ def set_pending
116
+ mutex.synchronize do
117
+ @state = :pending
118
+ @children.each { |c| c.set_pending }
119
+ end
120
+ end
121
+
122
+ # @!visibility private
123
+ def root? # :nodoc:
124
+ @parent.nil?
125
+ end
126
+
127
+ # @!visibility private
128
+ def on_fulfill(result)
129
+ realize Proc.new { @on_fulfill.call(result) }
130
+ nil
131
+ end
132
+
133
+ # @!visibility private
134
+ def on_reject(reason)
135
+ realize Proc.new { @on_reject.call(reason) }
136
+ nil
137
+ end
138
+
139
+ def notify_child(child)
140
+ if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) }
141
+ if_state(:rejected) { child.on_reject(@reason) }
142
+ end
143
+
144
+ # @!visibility private
145
+ def realize(task)
146
+ @executor.post do
147
+ success, value, reason = SafeTaskExecutor.new(task).execute
148
+
149
+ children_to_notify = mutex.synchronize do
150
+ set_state!(success, value, reason)
151
+ @children.dup
152
+ end
153
+
154
+ children_to_notify.each { |child| notify_child(child) }
155
+ end
156
+ end
157
+
158
+ def set_state!(success, value, reason)
159
+ set_state(success, value, reason)
160
+ event.set
161
+ end
162
+
163
+ def synchronized_set_state!(success, value, reason)
164
+ mutex.lock
165
+ set_state!(success, value, reason)
166
+ ensure
167
+ mutex.unlock
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,79 @@
1
+ require 'concurrent/ivar'
2
+ require 'concurrent/utility/timer'
3
+ require 'concurrent/executor/safe_task_executor'
4
+
5
+ module Concurrent
6
+
7
+ class ScheduledTask < IVar
8
+
9
+ attr_reader :schedule_time
10
+
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)
16
+
17
+ self.observers = CopyOnNotifyObserverSet.new
18
+ @intended_time = intended_time
19
+ @state = :unscheduled
20
+ @task = block
21
+ @executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
22
+ end
23
+
24
+ # @since 0.5.0
25
+ def execute
26
+ if compare_and_set_state(:pending, :unscheduled)
27
+ @schedule_time = TimerSet.calculate_schedule_time(@intended_time)
28
+ Concurrent::timer(@schedule_time.to_f - Time.now.to_f) { @executor.post &method(:process_task) }
29
+ self
30
+ end
31
+ end
32
+
33
+ # @since 0.5.0
34
+ def self.execute(intended_time, opts = {}, &block)
35
+ return ScheduledTask.new(intended_time, opts, &block).execute
36
+ end
37
+
38
+ def cancelled?
39
+ state == :cancelled
40
+ end
41
+
42
+ def in_progress?
43
+ state == :in_progress
44
+ end
45
+
46
+ def cancel
47
+ if_state(:unscheduled, :pending) do
48
+ @state = :cancelled
49
+ event.set
50
+ true
51
+ end
52
+ end
53
+ alias_method :stop, :cancel
54
+
55
+ def add_observer(*args, &block)
56
+ if_state(:unscheduled, :pending, :in_progress) do
57
+ observers.add_observer(*args, &block)
58
+ end
59
+ end
60
+
61
+ protected :set, :fail, :complete
62
+
63
+ private
64
+
65
+ def process_task
66
+ if compare_and_set_state(:in_progress, :pending)
67
+ success, val, reason = SafeTaskExecutor.new(@task).execute
68
+
69
+ mutex.synchronize do
70
+ set_state(success, val, 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
79
+ end
@@ -0,0 +1,341 @@
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
+ module Concurrent
8
+
9
+ # A very common currency pattern is to run a thread that performs a task at regular
10
+ # intervals. The thread that performs the task sleeps for the given interval then
11
+ # wakes up and performs the task. Lather, rinse, repeat... This pattern causes two
12
+ # problems. First, it is difficult to test the business logic of the task because the
13
+ # task itself is tightly coupled with the concurrency logic. Second, an exception in
14
+ # raised while performing the task can cause the entire thread to abend. In a
15
+ # long-running application where the task thread is intended to run for days/weeks/years
16
+ # a crashed task thread can pose a significant problem. `TimerTask` alleviates both problems.
17
+ #
18
+ # When a `TimerTask` is launched it starts a thread for monitoring the execution interval.
19
+ # The `TimerTask` thread does not perform the task, however. Instead, the TimerTask
20
+ # launches the task on a separate thread. Should the task experience an unrecoverable
21
+ # crash only the task thread will crash. This makes the `TimerTask` very fault tolerant
22
+ # Additionally, the `TimerTask` thread can respond to the success or failure of the task,
23
+ # performing logging or ancillary operations. `TimerTask` can also be configured with a
24
+ # timeout value allowing it to kill a task that runs too long.
25
+ #
26
+ # One other advantage of `TimerTask` is it forces the business logic to be completely decoupled
27
+ # from the concurrency logic. The business logic can be tested separately then passed to the
28
+ # `TimerTask` for scheduling and running.
29
+ #
30
+ # In some cases it may be necessary for a `TimerTask` to affect its own execution cycle.
31
+ # To facilitate this a reference to the task object is passed into the block as a block
32
+ # argument every time the task is executed.
33
+ #
34
+ # The `TimerTask` class includes the `Dereferenceable` mixin module so the result of
35
+ # the last execution is always available via the `#value` method. Derefencing options
36
+ # can be passed to the `TimerTask` during construction or at any later time using the
37
+ # `#set_deref_options` method.
38
+ #
39
+ # `TimerTask` supports notification through the Ruby standard library
40
+ # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html Observable}
41
+ # module. On execution the `TimerTask` will notify the observers
42
+ # with threes arguments: time of execution, the result of the block (or nil on failure),
43
+ # and any raised exceptions (or nil on success). If the timeout interval is exceeded
44
+ # the observer will receive a `Concurrent::TimeoutError` object as the third argument.
45
+ #
46
+ # @example Basic usage
47
+ # task = Concurrent::TimerTask.new{ puts 'Boom!' }
48
+ # task.run!
49
+ #
50
+ # task.execution_interval #=> 60 (default)
51
+ # task.timeout_interval #=> 30 (default)
52
+ #
53
+ # # wait 60 seconds...
54
+ # #=> 'Boom!'
55
+ #
56
+ # task.stop #=> true
57
+ #
58
+ # @example Configuring `:execution_interval` and `:timeout_interval`
59
+ # task = Concurrent::TimerTask.new(execution_interval: 5, timeout_interval: 5) do
60
+ # puts 'Boom!'
61
+ # end
62
+ #
63
+ # task.execution_interval #=> 5
64
+ # task.timeout_interval #=> 5
65
+ #
66
+ # @example Immediate execution with `:run_now`
67
+ # task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' }
68
+ # task.run!
69
+ #
70
+ # #=> 'Boom!'
71
+ #
72
+ # @example Last `#value` and `Dereferenceable` mixin
73
+ # task = Concurrent::TimerTask.new(
74
+ # dup_on_deref: true,
75
+ # execution_interval: 5
76
+ # ){ Time.now }
77
+ #
78
+ # task.run!
79
+ # Time.now #=> 2013-11-07 18:06:50 -0500
80
+ # sleep(10)
81
+ # task.value #=> 2013-11-07 18:06:55 -0500
82
+ #
83
+ # @example Controlling execution from within the block
84
+ # timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task|
85
+ # task.execution_interval.times{ print 'Boom! ' }
86
+ # print "\n"
87
+ # task.execution_interval += 1
88
+ # if task.execution_interval > 5
89
+ # puts 'Stopping...'
90
+ # task.stop
91
+ # end
92
+ # end
93
+ #
94
+ # timer_task.run # blocking call - this task will stop itself
95
+ # #=> Boom!
96
+ # #=> Boom! Boom!
97
+ # #=> Boom! Boom! Boom!
98
+ # #=> Boom! Boom! Boom! Boom!
99
+ # #=> Boom! Boom! Boom! Boom! Boom!
100
+ # #=> Stopping...
101
+ #
102
+ # @example Observation
103
+ # class TaskObserver
104
+ # def update(time, result, ex)
105
+ # if result
106
+ # print "(#{time}) Execution successfully returned #{result}\n"
107
+ # elsif ex.is_a?(Concurrent::TimeoutError)
108
+ # print "(#{time}) Execution timed out\n"
109
+ # else
110
+ # print "(#{time}) Execution failed with error #{ex}\n"
111
+ # end
112
+ # end
113
+ # end
114
+ #
115
+ # task = Concurrent::TimerTask.new(execution_interval: 1, timeout_interval: 1){ 42 }
116
+ # task.add_observer(TaskObserver.new)
117
+ # task.run!
118
+ #
119
+ # #=> (2013-10-13 19:08:58 -0400) Execution successfully returned 42
120
+ # #=> (2013-10-13 19:08:59 -0400) Execution successfully returned 42
121
+ # #=> (2013-10-13 19:09:00 -0400) Execution successfully returned 42
122
+ # task.stop
123
+ #
124
+ # task = Concurrent::TimerTask.new(execution_interval: 1, timeout_interval: 1){ sleep }
125
+ # task.add_observer(TaskObserver.new)
126
+ # task.run!
127
+ #
128
+ # #=> (2013-10-13 19:07:25 -0400) Execution timed out
129
+ # #=> (2013-10-13 19:07:27 -0400) Execution timed out
130
+ # #=> (2013-10-13 19:07:29 -0400) Execution timed out
131
+ # task.stop
132
+ #
133
+ # task = Concurrent::TimerTask.new(execution_interval: 1){ raise StandardError }
134
+ # task.add_observer(TaskObserver.new)
135
+ # task.run!
136
+ #
137
+ # #=> (2013-10-13 19:09:37 -0400) Execution failed with error StandardError
138
+ # #=> (2013-10-13 19:09:38 -0400) Execution failed with error StandardError
139
+ # #=> (2013-10-13 19:09:39 -0400) Execution failed with error StandardError
140
+ # task.stop
141
+ #
142
+ # @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
143
+ # @see http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html
144
+ class TimerTask
145
+ include Dereferenceable
146
+ include RubyExecutor
147
+ include Concurrent::Observable
148
+
149
+ # Default `:execution_interval` in seconds.
150
+ EXECUTION_INTERVAL = 60
151
+
152
+ # Default `:timeout_interval` in seconds.
153
+ TIMEOUT_INTERVAL = 30
154
+
155
+ # Create a new TimerTask with the given task and configuration.
156
+ #
157
+ # @!macro [attach] timer_task_initialize
158
+ # @param [Hash] opts the options defining task execution.
159
+ # @option opts [Integer] :execution_interval number of seconds between
160
+ # task executions (default: EXECUTION_INTERVAL)
161
+ # @option opts [Integer] :timeout_interval number of seconds a task can
162
+ # run before it is considered to have failed (default: TIMEOUT_INTERVAL)
163
+ # @option opts [Boolean] :run_now Whether to run the task immediately
164
+ # upon instantiation or to wait until the first # execution_interval
165
+ # has passed (default: false)
166
+ #
167
+ # @raise ArgumentError when no block is given.
168
+ #
169
+ # @yield to the block after :execution_interval seconds have passed since
170
+ # the last yield
171
+ # @yieldparam task a reference to the `TimerTask` instance so that the
172
+ # block can control its own lifecycle. Necessary since `self` will
173
+ # refer to the execution context of the block rather than the running
174
+ # `TimerTask`.
175
+ #
176
+ # @note Calls Concurrent::Dereferenceable# set_deref_options passing `opts`.
177
+ # All options supported by Concurrent::Dereferenceable can be set
178
+ # during object initialization.
179
+ #
180
+ # @return [TimerTask] the new `TimerTask`
181
+ #
182
+ # @see Concurrent::Dereferenceable# set_deref_options
183
+ def initialize(opts = {}, &task)
184
+ raise ArgumentError.new('no block given') unless block_given?
185
+
186
+ init_executor
187
+ set_deref_options(opts)
188
+
189
+ self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
190
+ self.timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
191
+ @run_now = opts[:now] || opts[:run_now]
192
+ @executor = Concurrent::SafeTaskExecutor.new(task)
193
+ @running = Concurrent::AtomicBoolean.new(false)
194
+
195
+ self.observers = CopyOnNotifyObserverSet.new
196
+ end
197
+
198
+ # Is the executor running?
199
+ #
200
+ # @return [Boolean] `true` when running, `false` when shutting down or shutdown
201
+ def running?
202
+ @running.true?
203
+ end
204
+
205
+ # Execute a previously created `TimerTask`.
206
+ #
207
+ # @return [TimerTask] a reference to `self`
208
+ #
209
+ # @example Instance and execute in separate steps
210
+ # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }
211
+ # task.running? #=> false
212
+ # task.execute
213
+ # task.running? #=> true
214
+ #
215
+ # @example Instance and execute in one line
216
+ # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }.execute
217
+ # task.running? #=> true
218
+ #
219
+ # @since 0.6.0
220
+ def execute
221
+ mutex.synchronize do
222
+ if @running.false?
223
+ @running.make_true
224
+ schedule_next_task(@run_now ? 0 : @execution_interval)
225
+ end
226
+ end
227
+ self
228
+ end
229
+
230
+ # Create and execute a new `TimerTask`.
231
+ #
232
+ # @!macro timer_task_initialize
233
+ #
234
+ # @example
235
+ # task = Concurrent::TimerTask.execute(execution_interval: 10){ print "Hello World\n" }
236
+ # task.running? #=> true
237
+ #
238
+ # @since 0.6.0
239
+ def self.execute(opts = {}, &task)
240
+ TimerTask.new(opts, &task).execute
241
+ end
242
+
243
+ # @!attribute [rw] execution_interval
244
+ # @return [Fixnum] Number of seconds after the task completes before the
245
+ # task is performed again.
246
+ def execution_interval
247
+ mutex.lock
248
+ @execution_interval
249
+ ensure
250
+ mutex.unlock
251
+ end
252
+
253
+ # @!attribute [rw] execution_interval
254
+ # @return [Fixnum] Number of seconds after the task completes before the
255
+ # task is performed again.
256
+ def execution_interval=(value)
257
+ if (value = value.to_f) <= 0.0
258
+ raise ArgumentError.new('must be greater than zero')
259
+ else
260
+ begin
261
+ mutex.lock
262
+ @execution_interval = value
263
+ ensure
264
+ mutex.unlock
265
+ end
266
+ end
267
+ end
268
+
269
+ # @!attribute [rw] timeout_interval
270
+ # @return [Fixnum] Number of seconds the task can run before it is
271
+ # considered to have failed.
272
+ def timeout_interval
273
+ mutex.lock
274
+ @timeout_interval
275
+ ensure
276
+ mutex.unlock
277
+ end
278
+
279
+ # @!attribute [rw] timeout_interval
280
+ # @return [Fixnum] Number of seconds the task can run before it is
281
+ # considered to have failed.
282
+ def timeout_interval=(value)
283
+ if (value = value.to_f) <= 0.0
284
+ raise ArgumentError.new('must be greater than zero')
285
+ else
286
+ begin
287
+ mutex.lock
288
+ @timeout_interval = value
289
+ ensure
290
+ mutex.unlock
291
+ end
292
+ end
293
+ end
294
+
295
+ private :post, :<<
296
+
297
+ protected
298
+
299
+ # @!visibility private
300
+ def shutdown_execution
301
+ @running.make_false
302
+ super
303
+ end
304
+
305
+ # @!visibility private
306
+ def kill_execution
307
+ @running.make_false
308
+ super
309
+ end
310
+
311
+ # @!visibility private
312
+ def schedule_next_task(interval = execution_interval)
313
+ Concurrent::timer(interval, Concurrent::Event.new, &method(:execute_task))
314
+ end
315
+
316
+ # @!visibility private
317
+ def execute_task(completion)
318
+ return unless @running.true?
319
+ Concurrent::timer(timeout_interval, completion, &method(:timeout_task))
320
+ success, value, reason = @executor.execute(self)
321
+ if completion.try?
322
+ self.value = value
323
+ schedule_next_task
324
+ time = Time.now
325
+ observers.notify_observers do
326
+ [time, self.value, reason]
327
+ end
328
+ end
329
+ end
330
+
331
+ # @!visibility private
332
+ def timeout_task(completion)
333
+ return unless @running.true?
334
+ if completion.try?
335
+ self.value = value
336
+ schedule_next_task
337
+ observers.notify_observers(Time.now, nil, Concurrent::TimeoutError.new)
338
+ end
339
+ end
340
+ end
341
+ end