concurrent-ruby 0.7.0-java

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 (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