delayed_job_master 2.0.3 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +31 -34
- data/.gitignore +0 -2
- data/CHANGELOG.md +13 -0
- data/README.md +46 -33
- data/delayed_job_master.gemspec +8 -5
- data/lib/delayed/master/callbacks.rb +37 -0
- data/lib/delayed/master/command.rb +28 -5
- data/lib/delayed/master/config.rb +51 -58
- data/lib/delayed/master/core.rb +146 -0
- data/lib/delayed/master/database.rb +72 -0
- data/lib/delayed/master/file_reopener.rb +18 -0
- data/lib/delayed/master/forker.rb +30 -19
- data/lib/delayed/master/job_checker.rb +91 -47
- data/lib/delayed/master/job_finder.rb +31 -0
- data/lib/delayed/master/job_listener.rb +31 -0
- data/lib/delayed/master/monitoring.rb +37 -36
- data/lib/delayed/master/postgresql/job_listener.rb +73 -0
- data/lib/delayed/master/postgresql/job_notifier.rb +45 -0
- data/lib/delayed/master/safe_array.rb +30 -0
- data/lib/delayed/master/signaler.rb +17 -7
- data/lib/delayed/master/sleep.rb +18 -0
- data/lib/delayed/master/worker/backend/active_record.rb +41 -0
- data/lib/delayed/master/worker/extension.rb +14 -0
- data/lib/delayed/master/worker/lifecycle.rb +10 -0
- data/lib/delayed/master/worker/plugins/all.rb +17 -0
- data/lib/delayed/master/worker/plugins/executor_wrapper.rb +23 -0
- data/lib/delayed/master/worker/plugins/memory_checker.rb +28 -0
- data/lib/delayed/master/worker/plugins/signal_handler.rb +35 -0
- data/lib/delayed/master/worker/plugins/status_notifier.rb +21 -0
- data/lib/delayed/master/worker/thread_pool.rb +64 -0
- data/lib/delayed/master/worker/thread_worker.rb +65 -0
- data/lib/delayed/master/worker.rb +8 -7
- data/lib/delayed/master/worker_setting.rb +72 -0
- data/lib/delayed/master.rb +7 -100
- data/lib/delayed_job_master/railtie.rb +16 -0
- data/lib/delayed_job_master/version.rb +5 -0
- data/lib/delayed_job_master.rb +15 -1
- data/lib/generators/delayed_job_master/templates/config.rb +14 -14
- data/lib/generators/delayed_job_master/templates/script +1 -1
- metadata +71 -17
- data/gemfiles/rails50.gemfile +0 -7
- data/gemfiles/rails51.gemfile +0 -7
- data/gemfiles/rails52.gemfile +0 -7
- data/lib/delayed/master/database_detector.rb +0 -37
- data/lib/delayed/master/job_counter.rb +0 -26
- data/lib/delayed/master/plugins/memory_checker.rb +0 -24
- data/lib/delayed/master/plugins/signal_handler.rb +0 -31
- data/lib/delayed/master/plugins/status_notifier.rb +0 -17
- data/lib/delayed/master/util/file_reopener.rb +0 -18
- data/lib/delayed/master/version.rb +0 -5
- data/lib/delayed/master/worker_extension.rb +0 -19
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'logger'
|
5
|
+
require_relative 'safe_array'
|
6
|
+
require_relative 'command'
|
7
|
+
require_relative 'worker'
|
8
|
+
require_relative 'database'
|
9
|
+
require_relative 'callbacks'
|
10
|
+
require_relative 'monitoring'
|
11
|
+
require_relative 'job_checker'
|
12
|
+
require_relative 'job_listener'
|
13
|
+
require_relative 'signaler'
|
14
|
+
require_relative 'file_reopener'
|
15
|
+
|
16
|
+
module Delayed
|
17
|
+
module Master
|
18
|
+
class Core
|
19
|
+
attr_reader :config, :logger, :databases, :callbacks, :workers
|
20
|
+
attr_reader :monitoring, :job_checker, :job_listener
|
21
|
+
|
22
|
+
def initialize(argv)
|
23
|
+
@config = Command.new(argv).config
|
24
|
+
@logger = setup_logger(@config.log_file, @config.log_level)
|
25
|
+
@workers = SafeArray.new
|
26
|
+
|
27
|
+
@databases = Database.all(@config.databases)
|
28
|
+
@callbacks = Callbacks.new(@config)
|
29
|
+
@monitoring = Monitoring.new(self)
|
30
|
+
@job_checker = JobChecker.new(self)
|
31
|
+
@job_listener = JobListener.klass.new(self)
|
32
|
+
@signaler = Signaler.new(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
print_config
|
37
|
+
daemonize if @config.daemon
|
38
|
+
|
39
|
+
@logger.info { "started master #{Process.pid}".tap { |msg| puts msg } }
|
40
|
+
|
41
|
+
handle_pid_file do
|
42
|
+
@signaler.register
|
43
|
+
@prepared = true
|
44
|
+
|
45
|
+
start
|
46
|
+
wait
|
47
|
+
shutdown
|
48
|
+
end
|
49
|
+
|
50
|
+
@logger.info { "shut down master" }
|
51
|
+
end
|
52
|
+
|
53
|
+
def start
|
54
|
+
@job_checker.start
|
55
|
+
@job_listener.start
|
56
|
+
@monitoring.start
|
57
|
+
end
|
58
|
+
|
59
|
+
def wait
|
60
|
+
@job_checker.wait
|
61
|
+
@job_listener.wait
|
62
|
+
@monitoring.wait
|
63
|
+
end
|
64
|
+
|
65
|
+
def shutdown
|
66
|
+
@job_checker.shutdown
|
67
|
+
@job_listener.shutdown
|
68
|
+
@monitoring.shutdown
|
69
|
+
end
|
70
|
+
|
71
|
+
def prepared?
|
72
|
+
@prepared
|
73
|
+
end
|
74
|
+
|
75
|
+
def quit
|
76
|
+
@signaler.dispatch(:KILL)
|
77
|
+
shutdown
|
78
|
+
@workers.clear
|
79
|
+
@stop = true
|
80
|
+
end
|
81
|
+
|
82
|
+
def stop
|
83
|
+
@signaler.dispatch(:TERM)
|
84
|
+
shutdown
|
85
|
+
@workers.clear
|
86
|
+
@stop = true
|
87
|
+
end
|
88
|
+
|
89
|
+
def graceful_stop
|
90
|
+
@signaler.dispatch(:TERM)
|
91
|
+
@stop = true
|
92
|
+
end
|
93
|
+
|
94
|
+
def stop?
|
95
|
+
@stop == true
|
96
|
+
end
|
97
|
+
|
98
|
+
def reopen_files
|
99
|
+
@signaler.dispatch(:USR1)
|
100
|
+
@logger.info { "reopening files..." }
|
101
|
+
FileReopener.reopen
|
102
|
+
@logger.info { "reopened" }
|
103
|
+
end
|
104
|
+
|
105
|
+
def restart
|
106
|
+
@signaler.dispatch(:USR2)
|
107
|
+
@logger.info { "restarting master..." }
|
108
|
+
exec(*([$0] + ARGV))
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def setup_logger(log_file, log_level)
|
114
|
+
FileUtils.mkdir_p(File.dirname(log_file)) if log_file.is_a?(String)
|
115
|
+
logger = Logger.new(log_file)
|
116
|
+
logger.level = log_level
|
117
|
+
logger
|
118
|
+
end
|
119
|
+
|
120
|
+
def print_config
|
121
|
+
@config.abstract_texts.each do |text|
|
122
|
+
@logger.info { text }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def daemonize
|
127
|
+
Process.daemon(true)
|
128
|
+
end
|
129
|
+
|
130
|
+
def handle_pid_file
|
131
|
+
create_pid_file
|
132
|
+
yield
|
133
|
+
remove_pid_file
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_pid_file
|
137
|
+
FileUtils.mkdir_p(File.dirname(@config.pid_file))
|
138
|
+
File.write(@config.pid_file, Process.pid)
|
139
|
+
end
|
140
|
+
|
141
|
+
def remove_pid_file
|
142
|
+
File.delete(@config.pid_file) if File.exist?(@config.pid_file)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
module Master
|
5
|
+
class Database
|
6
|
+
class_attribute :model_cache
|
7
|
+
self.model_cache = {}
|
8
|
+
|
9
|
+
attr_accessor :spec_name
|
10
|
+
|
11
|
+
def initialize(spec_name)
|
12
|
+
@spec_name = spec_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def model
|
16
|
+
cache_model do
|
17
|
+
define_model
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_connection
|
22
|
+
model.connection_pool.with_connection do |connection|
|
23
|
+
yield connection
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def cache_model
|
30
|
+
self.class.model_cache[@spec_name] ||= yield
|
31
|
+
end
|
32
|
+
|
33
|
+
def define_model
|
34
|
+
model = Class.new(Delayed::Job)
|
35
|
+
model_name = "DelayedJob#{@spec_name.capitalize}"
|
36
|
+
unless Delayed::Master.const_defined?(model_name)
|
37
|
+
Delayed::Master.const_set(model_name, model)
|
38
|
+
Delayed::Master.const_get(model_name).establish_connection(@spec_name)
|
39
|
+
end
|
40
|
+
Delayed::Master.const_get(model_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
def all(spec_names = nil)
|
45
|
+
spec_names = spec_names.presence || spec_names_with_delayed_job_table
|
46
|
+
spec_names.map { |spec_name| new(spec_name) }
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def spec_names_with_delayed_job_table
|
52
|
+
@spec_names_with_delayed_job_table ||= spec_names_without_replica.select do |spec_name|
|
53
|
+
exist_delayed_job_table?(spec_name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def spec_names_without_replica
|
58
|
+
configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
|
59
|
+
configs.reject(&:replica?).map do |c|
|
60
|
+
c.respond_to?(:name) ? c.name.to_sym : c.spec_name.to_sym
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def exist_delayed_job_table?(spec_name)
|
65
|
+
new(spec_name).with_connection do |connection|
|
66
|
+
connection.tables.include?('delayed_jobs')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
module Master
|
5
|
+
class FileReopener
|
6
|
+
class << self
|
7
|
+
def reopen
|
8
|
+
ObjectSpace.each_object(File) do |file|
|
9
|
+
next if file.closed? || !file.sync
|
10
|
+
file.reopen file.path, 'a+'
|
11
|
+
file.sync = true
|
12
|
+
file.flush
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,42 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'file_reopener'
|
4
|
+
|
1
5
|
module Delayed
|
2
|
-
|
6
|
+
module Master
|
3
7
|
class Forker
|
4
8
|
def initialize(master)
|
5
9
|
@master = master
|
6
10
|
@config = master.config
|
11
|
+
@callbacks = master.callbacks
|
7
12
|
end
|
8
13
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
def call(worker)
|
15
|
+
around_fork(worker) do
|
16
|
+
@callbacks.run(:before_fork, @master, worker)
|
17
|
+
worker.pid = fork do
|
18
|
+
@callbacks.run(:after_fork, @master, worker)
|
19
|
+
after_fork_at_child(worker)
|
20
|
+
worker.pid = Process.pid
|
21
|
+
worker.instance = create_instance(worker)
|
22
|
+
$0 = worker.process_title
|
23
|
+
worker.instance.start
|
24
|
+
end
|
25
|
+
end
|
15
26
|
end
|
16
27
|
|
17
28
|
private
|
18
29
|
|
19
|
-
def
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
30
|
+
def around_fork(worker)
|
31
|
+
@master.logger.info { "forking #{worker.name}..." }
|
32
|
+
yield
|
33
|
+
@master.logger.info { "forked #{worker.name} with pid #{worker.pid}" }
|
34
|
+
end
|
35
|
+
|
36
|
+
def after_fork_at_child(worker)
|
37
|
+
FileReopener.reopen
|
28
38
|
end
|
29
39
|
|
30
40
|
def create_instance(worker)
|
31
|
-
require_relative '
|
41
|
+
require_relative 'worker/extension'
|
32
42
|
|
33
|
-
instance = Delayed::Worker.new
|
43
|
+
instance = Delayed::Worker.new
|
34
44
|
[:max_run_time, :max_attempts, :destroy_failed_jobs].each do |key|
|
35
45
|
if (value = worker.setting.send(key))
|
36
46
|
Delayed::Worker.send("#{key}=", value)
|
37
47
|
end
|
38
48
|
end
|
39
|
-
[:
|
49
|
+
[:min_priority, :max_priority, :sleep_delay, :read_ahead, :exit_on_complete, :queues,
|
50
|
+
:max_threads, :max_memory].each do |key|
|
40
51
|
if (value = worker.setting.send(key))
|
41
52
|
instance.send("#{key}=", value)
|
42
53
|
end
|
@@ -1,83 +1,127 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'database'
|
4
|
+
require_relative 'forker'
|
5
|
+
require_relative 'job_finder'
|
6
|
+
require_relative 'sleep'
|
3
7
|
|
4
8
|
module Delayed
|
5
|
-
|
9
|
+
module Master
|
6
10
|
class JobChecker
|
11
|
+
include Sleep
|
12
|
+
|
7
13
|
def initialize(master)
|
8
14
|
@master = master
|
9
15
|
@config = master.config
|
10
|
-
@
|
16
|
+
@databases = master.databases
|
17
|
+
@callbacks = master.callbacks
|
18
|
+
@queues = @databases.map { |database| [database, Queue.new] }.to_h
|
19
|
+
@threads = []
|
20
|
+
end
|
11
21
|
|
12
|
-
|
13
|
-
|
22
|
+
def start
|
23
|
+
@threads << start_scheduler_thread
|
24
|
+
@threads += @databases.map do |database|
|
25
|
+
start_checker_thread(database)
|
26
|
+
end
|
14
27
|
end
|
15
28
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
29
|
+
def start_scheduler_thread
|
30
|
+
Thread.new do
|
31
|
+
loop_with_sleep @config.polling_interval do |i|
|
32
|
+
if @master.stop?
|
33
|
+
stop
|
34
|
+
break
|
35
|
+
elsif i == 0
|
36
|
+
schedule(@databases)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
19
41
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
42
|
+
def start_checker_thread(database)
|
43
|
+
Thread.new(database) do |database|
|
44
|
+
loop do
|
45
|
+
if @queues[database].pop == :stop
|
46
|
+
break
|
47
|
+
else
|
48
|
+
@callbacks.call(:polling, @master, database) do
|
49
|
+
check(database)
|
25
50
|
end
|
26
51
|
end
|
27
52
|
end
|
28
53
|
end
|
29
|
-
|
30
|
-
threads.each(&:join)
|
31
|
-
|
32
|
-
workers
|
33
54
|
end
|
34
55
|
|
35
|
-
|
56
|
+
def stop
|
57
|
+
@databases.each do |database|
|
58
|
+
queue = @queues[database]
|
59
|
+
queue.clear
|
60
|
+
queue.push(:stop)
|
61
|
+
end
|
62
|
+
end
|
36
63
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
unless Delayed::Master.const_defined?(klass_name)
|
42
|
-
Delayed::Master.const_set(klass_name, klass)
|
43
|
-
Delayed::Master.const_get(klass_name).establish_connection(spec_name)
|
44
|
-
end
|
64
|
+
def schedule(databases)
|
65
|
+
Array(databases).each do |database|
|
66
|
+
queue = @queues[database]
|
67
|
+
queue.push(database) if queue.size == 0
|
45
68
|
end
|
46
69
|
end
|
47
70
|
|
48
|
-
def
|
49
|
-
|
71
|
+
def wait
|
72
|
+
@threads.each(&:join)
|
50
73
|
end
|
51
74
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
75
|
+
def shutdown
|
76
|
+
@threads.each(&:kill)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def check(database)
|
82
|
+
free_settings = detect_free_settings(database)
|
83
|
+
return if free_settings.blank?
|
84
|
+
|
85
|
+
@master.logger.debug { "checking jobs @#{database.spec_name}..." }
|
86
|
+
settings = check_jobs(database, free_settings)
|
87
|
+
if settings.present?
|
88
|
+
@master.logger.info { "found jobs for #{settings.uniq.map(&:worker_info).join(', ')}" }
|
89
|
+
fork_workers(database, settings)
|
57
90
|
end
|
91
|
+
rescue => e
|
92
|
+
@master.logger.warn { "#{e.class}: #{e.message}" }
|
93
|
+
@master.logger.debug { e.backtrace.join("\n") }
|
58
94
|
end
|
59
95
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
96
|
+
def detect_free_settings(database)
|
97
|
+
@config.worker_settings.each_with_object([]) do |setting, array|
|
98
|
+
used_count = @master.workers.count { |worker| worker.setting.queues == setting.queues }
|
99
|
+
free_count = setting.max_processes - used_count
|
100
|
+
array << [setting, free_count] if free_count > 0
|
65
101
|
end
|
66
102
|
end
|
67
103
|
|
68
|
-
def
|
69
|
-
|
104
|
+
def check_jobs(database, settings)
|
105
|
+
finder = JobFinder.new(database.model)
|
70
106
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
yield setting
|
107
|
+
settings.each_with_object([]) do |(setting, free_count), array|
|
108
|
+
job_ids = finder.call(setting, free_count)
|
109
|
+
if job_ids.size > 0
|
110
|
+
[free_count, job_ids.size].min.times do
|
111
|
+
array << setting
|
77
112
|
end
|
78
113
|
end
|
79
114
|
end
|
80
115
|
end
|
116
|
+
|
117
|
+
def fork_workers(database, settings)
|
118
|
+
settings.each do |setting|
|
119
|
+
worker = Worker.new(database: database, setting: setting)
|
120
|
+
Forker.new(@master).call(worker)
|
121
|
+
@master.workers << worker
|
122
|
+
@master.monitoring.schedule(worker)
|
123
|
+
end
|
124
|
+
end
|
81
125
|
end
|
82
126
|
end
|
83
127
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# JobFinder runs SQL query which is almost same as delayed_job_active_record.
|
4
|
+
# See https://github.com/collectiveidea/delayed_job_active_record/blob/master/lib/delayed/backend/active_record.rb
|
5
|
+
module Delayed
|
6
|
+
module Master
|
7
|
+
class JobFinder
|
8
|
+
def initialize(model)
|
9
|
+
@model = model
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(setting, limit)
|
13
|
+
scope(setting).limit(limit).pluck(:id)
|
14
|
+
end
|
15
|
+
|
16
|
+
def count(setting)
|
17
|
+
scope(setting).count
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def scope(setting)
|
23
|
+
@model.ready_to_run(nil, setting.max_run_time || Delayed::Worker::DEFAULT_MAX_RUN_TIME).tap do |jobs|
|
24
|
+
jobs.where!("priority >= ?", setting.min_priority) if setting.min_priority
|
25
|
+
jobs.where!("priority <= ?", setting.max_priority) if setting.max_priority
|
26
|
+
jobs.where!(queue: setting.queues) if setting.queues.present?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
module Master
|
5
|
+
class JobListener
|
6
|
+
def initialize(master)
|
7
|
+
end
|
8
|
+
|
9
|
+
def start
|
10
|
+
end
|
11
|
+
|
12
|
+
def wait
|
13
|
+
end
|
14
|
+
|
15
|
+
def shutdown
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def klass
|
20
|
+
case DelayedJobMaster.config.listener
|
21
|
+
when :postgresql
|
22
|
+
require_relative 'postgresql/job_listener'
|
23
|
+
Postgresql::JobListener
|
24
|
+
else
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,58 +1,59 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'safe_array'
|
4
|
+
require_relative 'sleep'
|
3
5
|
|
4
6
|
module Delayed
|
5
|
-
|
7
|
+
module Master
|
6
8
|
class Monitoring
|
9
|
+
include Sleep
|
10
|
+
|
7
11
|
def initialize(master)
|
8
12
|
@master = master
|
9
13
|
@config = master.config
|
10
|
-
@
|
11
|
-
@
|
14
|
+
@callbacks = master.callbacks
|
15
|
+
@threads = SafeArray.new
|
12
16
|
end
|
13
17
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
def start
|
19
|
+
@threads << Thread.new do
|
20
|
+
loop_with_sleep @config.monitor_interval do |i|
|
21
|
+
if @master.stop?
|
22
|
+
break
|
23
|
+
elsif i == 0
|
24
|
+
@callbacks.call(:monitor, @master) {}
|
25
|
+
end
|
20
26
|
end
|
21
|
-
sleep @config.monitor_wait.to_i
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@config.run_callback(:after_monitor, @master)
|
31
|
-
rescue Exception => e
|
32
|
-
@master.logger.warn "#{e.class}: #{e.message} at #{__FILE__}: #{__LINE__}"
|
30
|
+
def schedule(worker)
|
31
|
+
@threads << Thread.new do
|
32
|
+
wait_pid(worker)
|
33
|
+
@threads.delete(Thread.current)
|
34
|
+
end
|
33
35
|
end
|
34
36
|
|
35
|
-
def
|
36
|
-
|
37
|
-
@master.logger.debug "found terminated pid: #{pid}"
|
38
|
-
@master.workers.reject! { |worker| worker.pid == pid }
|
39
|
-
end
|
37
|
+
def wait
|
38
|
+
@threads.each(&:join)
|
40
39
|
end
|
41
40
|
|
42
|
-
def
|
43
|
-
|
44
|
-
rescue Errno::ECHILD
|
45
|
-
nil
|
41
|
+
def shutdown
|
42
|
+
@threads.each(&:kill)
|
46
43
|
end
|
47
44
|
|
48
|
-
|
49
|
-
@master.logger.debug "checking jobs..."
|
45
|
+
private
|
50
46
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
47
|
+
def wait_pid(worker)
|
48
|
+
Process.waitpid(worker.pid)
|
49
|
+
@master.logger.debug { "found terminated pid: #{worker.pid}" }
|
50
|
+
@master.workers.delete(worker)
|
51
|
+
rescue Errno::ECHILD
|
52
|
+
@master.logger.warn { "failed to waitpid: #{worker.pid}" }
|
53
|
+
@master.workers.delete(worker)
|
54
|
+
rescue => e
|
55
|
+
@master.logger.warn { "#{e.class}: #{e.message}" }
|
56
|
+
@master.logger.debug { e.backtrace.join("\n") }
|
56
57
|
end
|
57
58
|
end
|
58
59
|
end
|