concurrent-ruby 0.7.0.rc0-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- 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
|