concurrent-ruby 0.7.0.rc0-x64-mingw32
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.
- checksums.yaml +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/ext/concurrent_ruby_ext/atomic_reference.c +78 -0
- data/ext/concurrent_ruby_ext/atomic_reference.h +12 -0
- data/ext/concurrent_ruby_ext/extconf.rb +59 -0
- data/ext/concurrent_ruby_ext/rb_concurrent.c +28 -0
- data/lib/2.0/concurrent_ruby_ext.so +0 -0
- data/lib/concurrent.rb +45 -0
- data/lib/concurrent/actress.rb +221 -0
- data/lib/concurrent/actress/ad_hoc.rb +20 -0
- data/lib/concurrent/actress/context.rb +98 -0
- data/lib/concurrent/actress/core.rb +228 -0
- data/lib/concurrent/actress/core_delegations.rb +42 -0
- data/lib/concurrent/actress/envelope.rb +41 -0
- data/lib/concurrent/actress/errors.rb +14 -0
- data/lib/concurrent/actress/reference.rb +64 -0
- data/lib/concurrent/actress/type_check.rb +48 -0
- data/lib/concurrent/agent.rb +232 -0
- data/lib/concurrent/async.rb +319 -0
- data/lib/concurrent/atomic.rb +46 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
- data/lib/concurrent/atomic/atomic_fixnum.rb +162 -0
- data/lib/concurrent/atomic/condition.rb +67 -0
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +118 -0
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +117 -0
- data/lib/concurrent/atomic/count_down_latch.rb +116 -0
- data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
- data/lib/concurrent/atomic/event.rb +98 -0
- data/lib/concurrent/atomic/thread_local_var.rb +117 -0
- data/lib/concurrent/atomic_reference/concurrent_update_error.rb +7 -0
- data/lib/concurrent/atomic_reference/delegated_update.rb +28 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +28 -0
- data/lib/concurrent/atomic_reference/jruby.rb +8 -0
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +47 -0
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +24 -0
- data/lib/concurrent/atomic_reference/rbx.rb +16 -0
- data/lib/concurrent/atomic_reference/ruby.rb +16 -0
- data/lib/concurrent/atomics.rb +10 -0
- data/lib/concurrent/channel/buffered_channel.rb +85 -0
- data/lib/concurrent/channel/channel.rb +41 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +34 -0
- data/lib/concurrent/channel/waitable_list.rb +40 -0
- data/lib/concurrent/channels.rb +5 -0
- data/lib/concurrent/collection/blocking_ring_buffer.rb +71 -0
- data/lib/concurrent/collection/priority_queue.rb +305 -0
- data/lib/concurrent/collection/ring_buffer.rb +59 -0
- data/lib/concurrent/collections.rb +3 -0
- data/lib/concurrent/configuration.rb +158 -0
- data/lib/concurrent/dataflow.rb +91 -0
- data/lib/concurrent/delay.rb +112 -0
- data/lib/concurrent/dereferenceable.rb +101 -0
- data/lib/concurrent/errors.rb +30 -0
- data/lib/concurrent/exchanger.rb +34 -0
- data/lib/concurrent/executor/cached_thread_pool.rb +44 -0
- data/lib/concurrent/executor/executor.rb +229 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +33 -0
- data/lib/concurrent/executor/immediate_executor.rb +16 -0
- data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +33 -0
- data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +187 -0
- data/lib/concurrent/executor/per_thread_executor.rb +24 -0
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +32 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +73 -0
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +286 -0
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +72 -0
- data/lib/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent/executor/serialized_execution.rb +90 -0
- data/lib/concurrent/executor/single_thread_executor.rb +35 -0
- data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
- data/lib/concurrent/executor/timer_set.rb +143 -0
- data/lib/concurrent/executors.rb +9 -0
- data/lib/concurrent/future.rb +124 -0
- data/lib/concurrent/ivar.rb +111 -0
- data/lib/concurrent/logging.rb +17 -0
- data/lib/concurrent/mvar.rb +200 -0
- data/lib/concurrent/obligation.rb +171 -0
- data/lib/concurrent/observable.rb +40 -0
- data/lib/concurrent/options_parser.rb +46 -0
- data/lib/concurrent/promise.rb +169 -0
- data/lib/concurrent/scheduled_task.rb +78 -0
- data/lib/concurrent/supervisor.rb +343 -0
- data/lib/concurrent/timer_task.rb +341 -0
- data/lib/concurrent/tvar.rb +252 -0
- data/lib/concurrent/utilities.rb +3 -0
- data/lib/concurrent/utility/processor_count.rb +150 -0
- data/lib/concurrent/utility/timeout.rb +35 -0
- data/lib/concurrent/utility/timer.rb +21 -0
- data/lib/concurrent/version.rb +3 -0
- data/lib/concurrent_ruby.rb +1 -0
- data/lib/concurrent_ruby_ext.so +0 -0
- data/lib/extension_helper.rb +9 -0
- metadata +141 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/obligation'
|
4
|
+
require 'concurrent/options_parser'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
class Promise
|
9
|
+
include Obligation
|
10
|
+
|
11
|
+
# Initialize a new Promise with the provided options.
|
12
|
+
#
|
13
|
+
# @param [Hash] opts the options used to define the behavior at update and deref
|
14
|
+
#
|
15
|
+
# @option opts [Promise] :parent the parent `Promise` when building a chain/tree
|
16
|
+
# @option opts [Proc] :on_fulfill fulfillment handler
|
17
|
+
# @option opts [Proc] :on_reject rejection handler
|
18
|
+
#
|
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
|
21
|
+
# global task pool (for short-running tasks)
|
22
|
+
# @option opts [object] :executor when provided will run all operations on
|
23
|
+
# this executor rather than the global thread pool (overrides :operation)
|
24
|
+
#
|
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
|
28
|
+
# returning the value returned from the proc
|
29
|
+
#
|
30
|
+
# @see http://wiki.commonjs.org/wiki/Promises/A
|
31
|
+
# @see http://promises-aplus.github.io/promises-spec/
|
32
|
+
def initialize(opts = {}, &block)
|
33
|
+
opts.delete_if { |k, v| v.nil? }
|
34
|
+
|
35
|
+
@executor = OptionsParser::get_executor_from(opts)
|
36
|
+
@parent = opts.fetch(:parent) { nil }
|
37
|
+
@on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
|
38
|
+
@on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
|
39
|
+
|
40
|
+
@promise_body = block || Proc.new { |result| result }
|
41
|
+
@state = :unscheduled
|
42
|
+
@children = []
|
43
|
+
|
44
|
+
init_obligation
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Promise]
|
48
|
+
def self.fulfill(value, opts = {})
|
49
|
+
Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, true, value, nil) }
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# @return [Promise]
|
54
|
+
def self.reject(reason, opts = {})
|
55
|
+
Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, false, nil, reason) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Promise]
|
59
|
+
# @since 0.5.0
|
60
|
+
def execute
|
61
|
+
if root?
|
62
|
+
if compare_and_set_state(:pending, :unscheduled)
|
63
|
+
set_pending
|
64
|
+
realize(@promise_body)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
@parent.execute
|
68
|
+
end
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
# @since 0.5.0
|
73
|
+
def self.execute(opts = {}, &block)
|
74
|
+
new(opts, &block).execute
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Promise] the new promise
|
78
|
+
def then(rescuer = nil, &block)
|
79
|
+
raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
|
80
|
+
block = Proc.new { |result| result } if block.nil?
|
81
|
+
child = Promise.new(
|
82
|
+
parent: self,
|
83
|
+
executor: @executor,
|
84
|
+
on_fulfill: block,
|
85
|
+
on_reject: rescuer
|
86
|
+
)
|
87
|
+
|
88
|
+
mutex.synchronize do
|
89
|
+
child.state = :pending if @state == :pending
|
90
|
+
child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled
|
91
|
+
child.on_reject(@reason) if @state == :rejected
|
92
|
+
@children << child
|
93
|
+
end
|
94
|
+
|
95
|
+
child
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [Promise]
|
99
|
+
def on_success(&block)
|
100
|
+
raise ArgumentError.new('no block given') unless block_given?
|
101
|
+
self.then &block
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [Promise]
|
105
|
+
def rescue(&block)
|
106
|
+
self.then(block)
|
107
|
+
end
|
108
|
+
|
109
|
+
alias_method :catch, :rescue
|
110
|
+
alias_method :on_error, :rescue
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
def set_pending
|
115
|
+
mutex.synchronize do
|
116
|
+
@state = :pending
|
117
|
+
@children.each { |c| c.set_pending }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# @!visibility private
|
122
|
+
def root? # :nodoc:
|
123
|
+
@parent.nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
# @!visibility private
|
127
|
+
def on_fulfill(result)
|
128
|
+
realize Proc.new { @on_fulfill.call(result) }
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# @!visibility private
|
133
|
+
def on_reject(reason)
|
134
|
+
realize Proc.new { @on_reject.call(reason) }
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def notify_child(child)
|
139
|
+
if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) }
|
140
|
+
if_state(:rejected) { child.on_reject(@reason) }
|
141
|
+
end
|
142
|
+
|
143
|
+
# @!visibility private
|
144
|
+
def realize(task)
|
145
|
+
@executor.post do
|
146
|
+
success, value, reason = SafeTaskExecutor.new(task).execute
|
147
|
+
|
148
|
+
children_to_notify = mutex.synchronize do
|
149
|
+
set_state!(success, value, reason)
|
150
|
+
@children.dup
|
151
|
+
end
|
152
|
+
|
153
|
+
children_to_notify.each { |child| notify_child(child) }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def set_state!(success, value, reason)
|
158
|
+
set_state(success, value, reason)
|
159
|
+
event.set
|
160
|
+
end
|
161
|
+
|
162
|
+
def synchronized_set_state!(success, value, reason)
|
163
|
+
mutex.lock
|
164
|
+
set_state!(success, value, reason)
|
165
|
+
ensure
|
166
|
+
mutex.unlock
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,78 @@
|
|
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
|
+
end
|
22
|
+
|
23
|
+
# @since 0.5.0
|
24
|
+
def execute
|
25
|
+
if compare_and_set_state(:pending, :unscheduled)
|
26
|
+
@schedule_time = TimerSet.calculate_schedule_time(@intended_time)
|
27
|
+
Concurrent::timer(@schedule_time.to_f - Time.now.to_f, &method(:process_task))
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @since 0.5.0
|
33
|
+
def self.execute(intended_time, opts = {}, &block)
|
34
|
+
return ScheduledTask.new(intended_time, opts, &block).execute
|
35
|
+
end
|
36
|
+
|
37
|
+
def cancelled?
|
38
|
+
state == :cancelled
|
39
|
+
end
|
40
|
+
|
41
|
+
def in_progress?
|
42
|
+
state == :in_progress
|
43
|
+
end
|
44
|
+
|
45
|
+
def cancel
|
46
|
+
if_state(:unscheduled, :pending) do
|
47
|
+
@state = :cancelled
|
48
|
+
event.set
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
alias_method :stop, :cancel
|
53
|
+
|
54
|
+
def add_observer(*args, &block)
|
55
|
+
if_state(:unscheduled, :pending, :in_progress) do
|
56
|
+
observers.add_observer(*args, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected :set, :fail, :complete
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def process_task
|
65
|
+
if compare_and_set_state(:in_progress, :pending)
|
66
|
+
success, val, reason = SafeTaskExecutor.new(@task).execute
|
67
|
+
|
68
|
+
mutex.synchronize do
|
69
|
+
set_state(success, val, reason)
|
70
|
+
event.set
|
71
|
+
end
|
72
|
+
|
73
|
+
time = Time.now
|
74
|
+
observers.notify_and_delete_observers{ [time, self.value, reason] }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/errors'
|
4
|
+
|
5
|
+
module Concurrent
|
6
|
+
|
7
|
+
class Supervisor
|
8
|
+
|
9
|
+
DEFAULT_MONITOR_INTERVAL = 1
|
10
|
+
RESTART_STRATEGIES = [:one_for_one, :one_for_all, :rest_for_one]
|
11
|
+
DEFAULT_MAX_RESTART = 5
|
12
|
+
DEFAULT_MAX_TIME = 60
|
13
|
+
WORKER_API = {run: 0, stop: 0, running?: 0}
|
14
|
+
|
15
|
+
CHILD_TYPES = [:worker, :supervisor]
|
16
|
+
CHILD_RESTART_OPTIONS = [:permanent, :transient, :temporary]
|
17
|
+
|
18
|
+
WorkerContext = Struct.new(:worker, :type, :restart) do
|
19
|
+
attr_accessor :thread
|
20
|
+
attr_accessor :terminated
|
21
|
+
|
22
|
+
def alive?() return thread && thread.alive?; end
|
23
|
+
|
24
|
+
def needs_restart?
|
25
|
+
return false if thread && thread.alive?
|
26
|
+
return false if terminated
|
27
|
+
case self.restart
|
28
|
+
when :permanent
|
29
|
+
return true
|
30
|
+
when :transient
|
31
|
+
return thread.nil? || thread.status.nil?
|
32
|
+
else #when :temporary
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
WorkerCounts = Struct.new(:specs, :supervisors, :workers) do
|
39
|
+
attr_accessor :status
|
40
|
+
def add(context)
|
41
|
+
self.specs += 1
|
42
|
+
self.supervisors += 1 if context.type == :supervisor
|
43
|
+
self.workers += 1 if context.type == :worker
|
44
|
+
end
|
45
|
+
def active() sleeping + running + aborting end
|
46
|
+
def sleeping() @status.reduce(0){|x, s| x += (s == 'sleep' ? 1 : 0) } end
|
47
|
+
def running() @status.reduce(0){|x, s| x += (s == 'run' ? 1 : 0) } end
|
48
|
+
def aborting() @status.reduce(0){|x, s| x += (s == 'aborting' ? 1 : 0) } end
|
49
|
+
def stopped() @status.reduce(0){|x, s| x += (s == false ? 1 : 0) } end
|
50
|
+
def abend() @status.reduce(0){|x, s| x += (s.nil? ? 1 : 0) } end
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :monitor_interval
|
54
|
+
attr_reader :restart_strategy
|
55
|
+
attr_reader :max_restart
|
56
|
+
attr_reader :max_time
|
57
|
+
|
58
|
+
alias_method :strategy, :restart_strategy
|
59
|
+
alias_method :max_r, :max_restart
|
60
|
+
alias_method :max_t, :max_time
|
61
|
+
|
62
|
+
def initialize(opts = {})
|
63
|
+
warn '[EXPERIMENTAL] Supervisor is being completely rewritten and will change soon.'
|
64
|
+
@restart_strategy = opts[:restart_strategy] || opts[:strategy] || :one_for_one
|
65
|
+
@monitor_interval = (opts[:monitor_interval] || DEFAULT_MONITOR_INTERVAL).to_f
|
66
|
+
@max_restart = (opts[:max_restart] || opts[:max_r] || DEFAULT_MAX_RESTART).to_i
|
67
|
+
@max_time = (opts[:max_time] || opts[:max_t] || DEFAULT_MAX_TIME).to_i
|
68
|
+
|
69
|
+
raise ArgumentError.new(":#{@restart_strategy} is not a valid restart strategy") unless RESTART_STRATEGIES.include?(@restart_strategy)
|
70
|
+
raise ArgumentError.new(':monitor_interval must be greater than zero') unless @monitor_interval > 0.0
|
71
|
+
raise ArgumentError.new(':max_restart must be greater than zero') unless @max_restart > 0
|
72
|
+
raise ArgumentError.new(':max_time must be greater than zero') unless @max_time > 0
|
73
|
+
|
74
|
+
@running = false
|
75
|
+
@mutex = Mutex.new
|
76
|
+
@workers = []
|
77
|
+
@monitor = nil
|
78
|
+
|
79
|
+
@count = WorkerCounts.new(0, 0, 0)
|
80
|
+
@restart_times = []
|
81
|
+
|
82
|
+
add_worker(opts[:worker]) unless opts[:worker].nil?
|
83
|
+
add_workers(opts[:workers]) unless opts[:workers].nil?
|
84
|
+
end
|
85
|
+
|
86
|
+
def run!
|
87
|
+
@mutex.synchronize do
|
88
|
+
raise StandardError.new('already running') if @running
|
89
|
+
@running = true
|
90
|
+
@monitor = Thread.new do
|
91
|
+
Thread.current.abort_on_exception = false
|
92
|
+
monitor
|
93
|
+
end
|
94
|
+
end
|
95
|
+
Thread.pass
|
96
|
+
end
|
97
|
+
|
98
|
+
def run
|
99
|
+
@mutex.synchronize do
|
100
|
+
raise StandardError.new('already running') if @running
|
101
|
+
@running = true
|
102
|
+
end
|
103
|
+
monitor
|
104
|
+
true
|
105
|
+
end
|
106
|
+
|
107
|
+
def stop
|
108
|
+
@mutex.synchronize do
|
109
|
+
return true unless @running
|
110
|
+
|
111
|
+
@running = false
|
112
|
+
unless @monitor.nil?
|
113
|
+
@monitor.run if @monitor.status == 'sleep'
|
114
|
+
if @monitor.join(0.1).nil?
|
115
|
+
@monitor.kill
|
116
|
+
end
|
117
|
+
@monitor = nil
|
118
|
+
end
|
119
|
+
@restart_times.clear
|
120
|
+
|
121
|
+
@workers.length.times do |i|
|
122
|
+
context = @workers[-1-i]
|
123
|
+
terminate_worker(context)
|
124
|
+
end
|
125
|
+
prune_workers
|
126
|
+
end
|
127
|
+
|
128
|
+
true
|
129
|
+
end
|
130
|
+
|
131
|
+
def running?
|
132
|
+
@mutex.synchronize { @running }
|
133
|
+
end
|
134
|
+
|
135
|
+
def length
|
136
|
+
@mutex.synchronize { @workers.length }
|
137
|
+
end
|
138
|
+
alias_method :size, :length
|
139
|
+
|
140
|
+
def current_restart_count
|
141
|
+
@restart_times.length
|
142
|
+
end
|
143
|
+
|
144
|
+
def count
|
145
|
+
@mutex.synchronize do
|
146
|
+
@count.status = @workers.collect{|w| w.thread ? w.thread.status : false }
|
147
|
+
@count.dup.freeze
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_worker(worker, opts = {})
|
152
|
+
return nil if worker.nil? || ! behaves_as_worker?(worker)
|
153
|
+
@mutex.synchronize {
|
154
|
+
restart = opts[:restart] || :permanent
|
155
|
+
type = opts[:type] || (worker.is_a?(Supervisor) ? :supervisor : nil) || :worker
|
156
|
+
raise ArgumentError.new(":#{restart} is not a valid restart option") unless CHILD_RESTART_OPTIONS.include?(restart)
|
157
|
+
raise ArgumentError.new(":#{type} is not a valid child type") unless CHILD_TYPES.include?(type)
|
158
|
+
context = WorkerContext.new(worker, type, restart)
|
159
|
+
@workers << context
|
160
|
+
@count.add(context)
|
161
|
+
worker.run if @running
|
162
|
+
context.object_id
|
163
|
+
}
|
164
|
+
end
|
165
|
+
alias_method :add_child, :add_worker
|
166
|
+
|
167
|
+
def add_workers(workers, opts = {})
|
168
|
+
workers.collect do |worker|
|
169
|
+
add_worker(worker, opts)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
alias_method :add_children, :add_workers
|
173
|
+
|
174
|
+
def remove_worker(worker_id)
|
175
|
+
@mutex.synchronize do
|
176
|
+
index, context = find_worker(worker_id)
|
177
|
+
break(nil) if context.nil?
|
178
|
+
break(false) if context.alive?
|
179
|
+
@workers.delete_at(index)
|
180
|
+
context.worker
|
181
|
+
end
|
182
|
+
end
|
183
|
+
alias_method :remove_child, :remove_worker
|
184
|
+
|
185
|
+
def stop_worker(worker_id)
|
186
|
+
@mutex.synchronize do
|
187
|
+
return true unless @running
|
188
|
+
|
189
|
+
index, context = find_worker(worker_id)
|
190
|
+
break(nil) if index.nil?
|
191
|
+
context.terminated = true
|
192
|
+
terminate_worker(context)
|
193
|
+
@workers.delete_at(index) if @workers[index].restart == :temporary
|
194
|
+
true
|
195
|
+
end
|
196
|
+
end
|
197
|
+
alias_method :stop_child, :stop_worker
|
198
|
+
|
199
|
+
def start_worker(worker_id)
|
200
|
+
@mutex.synchronize do
|
201
|
+
return false unless @running
|
202
|
+
|
203
|
+
index, context = find_worker(worker_id)
|
204
|
+
break(nil) if context.nil?
|
205
|
+
context.terminated = false
|
206
|
+
run_worker(context) unless context.alive?
|
207
|
+
true
|
208
|
+
end
|
209
|
+
end
|
210
|
+
alias_method :start_child, :start_worker
|
211
|
+
|
212
|
+
def restart_worker(worker_id)
|
213
|
+
@mutex.synchronize do
|
214
|
+
return false unless @running
|
215
|
+
|
216
|
+
index, context = find_worker(worker_id)
|
217
|
+
break(nil) if context.nil?
|
218
|
+
break(false) if context.restart == :temporary
|
219
|
+
context.terminated = false
|
220
|
+
terminate_worker(context)
|
221
|
+
run_worker(context)
|
222
|
+
true
|
223
|
+
end
|
224
|
+
end
|
225
|
+
alias_method :restart_child, :restart_worker
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def behaves_as_worker?(obj)
|
230
|
+
WORKER_API.each do |method, arity|
|
231
|
+
break(false) unless obj.respond_to?(method) && obj.method(method).arity == arity
|
232
|
+
true
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def monitor
|
237
|
+
@workers.each{|context| run_worker(context)}
|
238
|
+
loop do
|
239
|
+
sleep(@monitor_interval)
|
240
|
+
break unless running?
|
241
|
+
@mutex.synchronize do
|
242
|
+
prune_workers
|
243
|
+
self.send(@restart_strategy)
|
244
|
+
end
|
245
|
+
break unless running?
|
246
|
+
end
|
247
|
+
rescue MaxRestartFrequencyError => ex
|
248
|
+
stop
|
249
|
+
end
|
250
|
+
|
251
|
+
def run_worker(context)
|
252
|
+
context.thread = Thread.new do
|
253
|
+
Thread.current.abort_on_exception = false
|
254
|
+
context.worker.run
|
255
|
+
end
|
256
|
+
context
|
257
|
+
end
|
258
|
+
|
259
|
+
def terminate_worker(context)
|
260
|
+
if context.alive?
|
261
|
+
context.worker.stop
|
262
|
+
Thread.pass
|
263
|
+
end
|
264
|
+
rescue Exception => ex
|
265
|
+
begin
|
266
|
+
Thread.kill(context.thread)
|
267
|
+
rescue
|
268
|
+
# suppress
|
269
|
+
end
|
270
|
+
ensure
|
271
|
+
context.thread = nil
|
272
|
+
end
|
273
|
+
|
274
|
+
def prune_workers
|
275
|
+
@workers.delete_if{|w| w.restart == :temporary && ! w.alive? }
|
276
|
+
end
|
277
|
+
|
278
|
+
def find_worker(worker_id)
|
279
|
+
index = @workers.find_index{|worker| worker.object_id == worker_id}
|
280
|
+
if index.nil?
|
281
|
+
[nil, nil]
|
282
|
+
else
|
283
|
+
[index, @workers[index]]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def exceeded_max_restart_frequency?
|
288
|
+
@restart_times.unshift(Time.now.to_i)
|
289
|
+
diff = (@restart_times.first - @restart_times.last).abs
|
290
|
+
if @restart_times.length >= @max_restart && diff <= @max_time
|
291
|
+
return true
|
292
|
+
elsif diff >= @max_time
|
293
|
+
@restart_times.pop
|
294
|
+
end
|
295
|
+
false
|
296
|
+
end
|
297
|
+
|
298
|
+
#----------------------------------------------------------------
|
299
|
+
# restart strategies
|
300
|
+
|
301
|
+
def one_for_one
|
302
|
+
@workers.each do |context|
|
303
|
+
if context.needs_restart?
|
304
|
+
raise MaxRestartFrequencyError if exceeded_max_restart_frequency?
|
305
|
+
run_worker(context)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def one_for_all
|
311
|
+
restart = false
|
312
|
+
|
313
|
+
restart = @workers.each do |context|
|
314
|
+
if context.needs_restart?
|
315
|
+
raise MaxRestartFrequencyError if exceeded_max_restart_frequency?
|
316
|
+
break(true)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
if restart
|
321
|
+
@workers.each do |context|
|
322
|
+
terminate_worker(context)
|
323
|
+
end
|
324
|
+
@workers.each{|context| run_worker(context)}
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def rest_for_one
|
329
|
+
restart = false
|
330
|
+
|
331
|
+
@workers.each do |context|
|
332
|
+
if restart
|
333
|
+
terminate_worker(context)
|
334
|
+
elsif context.needs_restart?
|
335
|
+
raise MaxRestartFrequencyError if exceeded_max_restart_frequency?
|
336
|
+
restart = true
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
one_for_one if restart
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|