resque-scheduler 2.2.0 → 4.10.2
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 +7 -0
- data/.github/dependabot.yml +12 -0
- data/.github/funding.yml +4 -0
- data/.github/workflows/codeql-analysis.yml +59 -0
- data/.github/workflows/rubocop.yml +27 -0
- data/.github/workflows/ruby.yml +81 -0
- data/AUTHORS.md +31 -0
- data/CHANGELOG.md +539 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +26 -2
- data/LICENSE +1 -1
- data/README.md +423 -91
- data/Rakefile +12 -35
- data/exe/resque-scheduler +5 -0
- data/lib/resque/scheduler/cli.rb +147 -0
- data/lib/resque/scheduler/configuration.rb +102 -0
- data/lib/resque/scheduler/delaying_extensions.rb +371 -0
- data/lib/resque/scheduler/env.rb +85 -0
- data/lib/resque/scheduler/extension.rb +13 -0
- data/lib/resque/scheduler/failure_handler.rb +11 -0
- data/lib/resque/scheduler/lock/base.rb +12 -3
- data/lib/resque/scheduler/lock/basic.rb +4 -5
- data/lib/resque/scheduler/lock/resilient.rb +52 -43
- data/lib/resque/scheduler/lock.rb +2 -1
- data/lib/resque/scheduler/locking.rb +104 -0
- data/lib/resque/scheduler/logger_builder.rb +83 -0
- data/lib/resque/scheduler/plugin.rb +31 -0
- data/lib/resque/scheduler/scheduling_extensions.rb +142 -0
- data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed.erb +23 -8
- data/lib/resque/scheduler/server/views/delayed_schedules.erb +20 -0
- data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_timestamp.erb +1 -1
- data/lib/resque/scheduler/server/views/scheduler.erb +58 -0
- data/lib/resque/scheduler/server/views/search.erb +69 -0
- data/lib/resque/scheduler/server/views/search_form.erb +4 -0
- data/lib/resque/scheduler/server.rb +268 -0
- data/lib/resque/scheduler/signal_handling.rb +40 -0
- data/lib/resque/scheduler/tasks.rb +25 -0
- data/lib/resque/scheduler/util.rb +39 -0
- data/lib/resque/scheduler/version.rb +7 -0
- data/lib/resque/scheduler.rb +333 -149
- data/lib/resque-scheduler.rb +4 -0
- data/resque-scheduler.gemspec +56 -20
- metadata +205 -104
- data/.gitignore +0 -8
- data/.rubocop.yml +0 -120
- data/.travis.yml +0 -10
- data/HISTORY.md +0 -180
- data/lib/resque/scheduler_locking.rb +0 -90
- data/lib/resque_scheduler/logger_builder.rb +0 -51
- data/lib/resque_scheduler/plugin.rb +0 -25
- data/lib/resque_scheduler/server/views/scheduler.erb +0 -44
- data/lib/resque_scheduler/server.rb +0 -92
- data/lib/resque_scheduler/tasks.rb +0 -40
- data/lib/resque_scheduler/version.rb +0 -3
- data/lib/resque_scheduler.rb +0 -355
- data/script/migrate_to_timestamps_set.rb +0 -14
- data/tasks/resque_scheduler.rake +0 -2
- data/test/delayed_queue_test.rb +0 -383
- data/test/redis-test.conf +0 -108
- data/test/resque-web_test.rb +0 -116
- data/test/scheduler_args_test.rb +0 -156
- data/test/scheduler_hooks_test.rb +0 -23
- data/test/scheduler_locking_test.rb +0 -180
- data/test/scheduler_setup_test.rb +0 -59
- data/test/scheduler_test.rb +0 -256
- data/test/support/redis_instance.rb +0 -129
- data/test/test_helper.rb +0 -92
- /data/lib/{resque_scheduler → resque/scheduler}/server/views/requeue-params.erb +0 -0
data/lib/resque/scheduler.rb
CHANGED
@@ -1,49 +1,43 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
|
3
|
+
require 'redis/errors'
|
1
4
|
require 'rufus/scheduler'
|
2
|
-
|
3
|
-
|
5
|
+
require_relative 'scheduler/configuration'
|
6
|
+
require_relative 'scheduler/locking'
|
7
|
+
require_relative 'scheduler/logger_builder'
|
8
|
+
require_relative 'scheduler/signal_handling'
|
9
|
+
require_relative 'scheduler/failure_handler'
|
4
10
|
|
5
11
|
module Resque
|
12
|
+
module Scheduler
|
13
|
+
autoload :Cli, 'resque/scheduler/cli'
|
14
|
+
autoload :Extension, 'resque/scheduler/extension'
|
15
|
+
autoload :Util, 'resque/scheduler/util'
|
16
|
+
autoload :VERSION, 'resque/scheduler/version'
|
17
|
+
INTERMITTENT_ERRORS = [
|
18
|
+
Errno::EAGAIN, Errno::ECONNRESET, Redis::CannotConnectError, Redis::TimeoutError
|
19
|
+
].freeze
|
6
20
|
|
7
|
-
|
8
|
-
|
9
|
-
extend Resque::SchedulerLocking
|
10
|
-
|
11
|
-
class << self
|
12
|
-
|
13
|
-
# If true, logs more stuff...
|
14
|
-
attr_accessor :verbose
|
15
|
-
|
16
|
-
# If set, produces no output
|
17
|
-
attr_accessor :mute
|
21
|
+
private
|
18
22
|
|
19
|
-
|
20
|
-
|
23
|
+
extend Resque::Scheduler::Locking
|
24
|
+
extend Resque::Scheduler::Configuration
|
25
|
+
extend Resque::Scheduler::SignalHandling
|
21
26
|
|
22
|
-
|
23
|
-
attr_accessor :dynamic
|
24
|
-
|
25
|
-
# Amount of time in seconds to sleep between polls of the delayed
|
26
|
-
# queue. Defaults to 5
|
27
|
-
attr_writer :poll_sleep_amount
|
27
|
+
public
|
28
28
|
|
29
|
+
class << self
|
29
30
|
attr_writer :logger
|
30
31
|
|
31
32
|
# the Rufus::Scheduler jobs that are scheduled
|
32
|
-
|
33
|
-
@@scheduled_jobs
|
34
|
-
end
|
33
|
+
attr_reader :scheduled_jobs
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
def logger
|
41
|
-
@logger ||= ResqueScheduler::LoggerBuilder.new(:mute => mute, :verbose => verbose, :log_dev => logfile).build
|
42
|
-
end
|
35
|
+
# allow user to set an additional failure handler
|
36
|
+
attr_writer :failure_handler
|
43
37
|
|
44
38
|
# Schedule all jobs and continually look for delayed jobs (never returns)
|
45
39
|
def run
|
46
|
-
|
40
|
+
procline 'Starting'
|
47
41
|
|
48
42
|
# trap signals
|
49
43
|
register_signal_handlers
|
@@ -54,52 +48,45 @@ module Resque
|
|
54
48
|
$stdout.sync = true
|
55
49
|
$stderr.sync = true
|
56
50
|
|
57
|
-
|
58
|
-
# If dynamic is set, load that schedule otherwise use normal load
|
59
|
-
if dynamic
|
60
|
-
reload_schedule!
|
61
|
-
else
|
62
|
-
load_schedule!
|
63
|
-
end
|
51
|
+
was_master = nil
|
64
52
|
|
65
|
-
|
66
|
-
|
67
|
-
if is_master?
|
68
|
-
begin
|
69
|
-
handle_delayed_items
|
70
|
-
update_schedule if dynamic
|
71
|
-
rescue Errno::EAGAIN, Errno::ECONNRESET => e
|
72
|
-
warn e.message
|
73
|
-
end
|
74
|
-
end
|
75
|
-
poll_sleep
|
76
|
-
end
|
53
|
+
begin
|
54
|
+
@th = Thread.current
|
77
55
|
|
78
|
-
|
79
|
-
|
56
|
+
# Now start the scheduling part of the loop.
|
57
|
+
loop do
|
58
|
+
begin
|
59
|
+
# Check on changes to master/child
|
60
|
+
@am_master = master?
|
61
|
+
if am_master != was_master
|
62
|
+
procline am_master ? 'Master scheduler' : 'Child scheduler'
|
80
63
|
|
64
|
+
# Load schedule because changed
|
65
|
+
reload_schedule!
|
66
|
+
end
|
81
67
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
68
|
+
if am_master
|
69
|
+
handle_delayed_items
|
70
|
+
update_schedule if dynamic
|
71
|
+
end
|
72
|
+
was_master = am_master
|
73
|
+
rescue *INTERMITTENT_ERRORS => e
|
74
|
+
log! e.message
|
75
|
+
release_master_lock
|
76
|
+
end
|
77
|
+
poll_sleep
|
78
|
+
end
|
88
79
|
|
89
|
-
|
90
|
-
|
91
|
-
trap('USR1') { print_schedule }
|
92
|
-
trap('USR2') { reload_schedule! }
|
93
|
-
rescue ArgumentError
|
94
|
-
warn "Signals QUIT and USR1 and USR2 not supported."
|
80
|
+
rescue Interrupt
|
81
|
+
log 'Exiting'
|
95
82
|
end
|
96
83
|
end
|
97
84
|
|
98
85
|
def print_schedule
|
99
86
|
if rufus_scheduler
|
100
87
|
log! "Scheduling Info\tLast Run"
|
101
|
-
scheduler_jobs = rufus_scheduler.
|
102
|
-
scheduler_jobs.each do |
|
88
|
+
scheduler_jobs = rufus_scheduler.jobs
|
89
|
+
scheduler_jobs.each do |_k, v|
|
103
90
|
log! "#{v.t}\t#{v.last}\t"
|
104
91
|
end
|
105
92
|
end
|
@@ -108,20 +95,20 @@ module Resque
|
|
108
95
|
# Pulls the schedule from Resque.schedule and loads it into the
|
109
96
|
# rufus scheduler instance
|
110
97
|
def load_schedule!
|
111
|
-
procline
|
98
|
+
procline 'Loading Schedule'
|
112
99
|
|
113
100
|
# Need to load the schedule from redis for the first time if dynamic
|
114
101
|
Resque.reload_schedule! if dynamic
|
115
102
|
|
116
|
-
log!
|
103
|
+
log! 'Schedule empty! Set Resque.schedule' if Resque.schedule.empty?
|
117
104
|
|
118
|
-
|
105
|
+
@scheduled_jobs = {}
|
119
106
|
|
120
107
|
Resque.schedule.each do |name, config|
|
121
108
|
load_schedule_job(name, config)
|
122
109
|
end
|
123
|
-
Resque.redis.del(:schedules_changed)
|
124
|
-
procline
|
110
|
+
Resque.redis.del(:schedules_changed) if am_master && dynamic
|
111
|
+
procline 'Schedules Loaded'
|
125
112
|
end
|
126
113
|
|
127
114
|
# modify interval type value to value with options if options available
|
@@ -130,75 +117,175 @@ module Resque
|
|
130
117
|
if args.is_a?(::Array)
|
131
118
|
return args.first if args.size > 2 || !args.last.is_a?(::Hash)
|
132
119
|
# symbolize keys of hash for options
|
133
|
-
args[
|
120
|
+
args[2] = args[1].reduce({}) do |m, i|
|
134
121
|
key, value = i
|
135
|
-
m[(key.to_sym
|
122
|
+
m[(key.respond_to?(:to_sym) ? key.to_sym : key) || key] = value
|
136
123
|
m
|
137
124
|
end
|
125
|
+
|
126
|
+
args[2][:job] = true
|
127
|
+
args[1] = nil
|
138
128
|
end
|
139
129
|
args
|
140
130
|
end
|
141
131
|
|
142
|
-
# Loads a job schedule into the Rufus::Scheduler and stores it
|
132
|
+
# Loads a job schedule into the Rufus::Scheduler and stores it
|
133
|
+
# in @scheduled_jobs
|
143
134
|
def load_schedule_job(name, config)
|
144
|
-
# If rails_env is set in the config,
|
145
|
-
#
|
146
|
-
# job should be scheduled regardless of
|
147
|
-
#
|
148
|
-
|
135
|
+
# If `rails_env` or `env` is set in the config, load jobs only if they
|
136
|
+
# are meant to be loaded in `Resque::Scheduler.env`. If `rails_env` or
|
137
|
+
# `env` is missing, the job should be scheduled regardless of the value
|
138
|
+
# of `Resque::Scheduler.env`.
|
139
|
+
|
140
|
+
configured_env = config['rails_env'] || config['env']
|
141
|
+
|
142
|
+
if configured_env.nil? || env_matches?(configured_env)
|
149
143
|
log! "Scheduling #{name} "
|
150
144
|
interval_defined = false
|
151
|
-
interval_types = %w
|
145
|
+
interval_types = %w(cron every)
|
152
146
|
interval_types.each do |interval_type|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
end
|
160
|
-
end
|
161
|
-
interval_defined = true
|
162
|
-
break
|
147
|
+
next unless !config[interval_type].nil? && !config[interval_type].empty?
|
148
|
+
args = optionizate_interval_value(config[interval_type])
|
149
|
+
args = [args, nil, job: true] if args.is_a?(::String)
|
150
|
+
|
151
|
+
job = rufus_scheduler.send(interval_type, *args) do
|
152
|
+
enqueue_recurring(name, config)
|
163
153
|
end
|
154
|
+
@scheduled_jobs[name] = job
|
155
|
+
interval_defined = true
|
156
|
+
break
|
164
157
|
end
|
165
158
|
unless interval_defined
|
166
|
-
log! "no #{interval_types.join(' / ')} found for
|
159
|
+
log! "no #{interval_types.join(' / ')} found for " \
|
160
|
+
"#{config['class']} (#{name}) - skipping"
|
167
161
|
end
|
162
|
+
else
|
163
|
+
log "Skipping schedule of #{name} because configured " \
|
164
|
+
"env #{configured_env.inspect} does not match current " \
|
165
|
+
"env #{env.inspect}"
|
168
166
|
end
|
169
167
|
end
|
170
168
|
|
171
|
-
# Returns true if the given schedule config hash matches the current
|
172
|
-
# ENV['RAILS_ENV']
|
169
|
+
# Returns true if the given schedule config hash matches the current env
|
173
170
|
def rails_env_matches?(config)
|
174
|
-
|
171
|
+
warn '`Resque::Scheduler.rails_env_matches?` is deprecated. ' \
|
172
|
+
'Please use `Resque::Scheduler.env_matches?` instead.'
|
173
|
+
config['rails_env'] && env &&
|
174
|
+
config['rails_env'].split(/[\s,]+/).include?(env)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns true if the current env is non-nil and the configured env
|
178
|
+
# (which is a comma-split string) includes the current env.
|
179
|
+
def env_matches?(configured_env)
|
180
|
+
env && configured_env.split(/[\s,]+/).include?(env)
|
175
181
|
end
|
176
182
|
|
177
183
|
# Handles queueing delayed items
|
178
184
|
# at_time - Time to start scheduling items (default: now).
|
179
|
-
def handle_delayed_items(at_time=nil)
|
180
|
-
|
181
|
-
|
182
|
-
|
185
|
+
def handle_delayed_items(at_time = nil)
|
186
|
+
timestamp = Resque.next_delayed_timestamp(at_time)
|
187
|
+
if timestamp
|
188
|
+
procline 'Processing Delayed Items'
|
189
|
+
until timestamp.nil?
|
183
190
|
enqueue_delayed_items_for_timestamp(timestamp)
|
184
191
|
timestamp = Resque.next_delayed_timestamp(at_time)
|
185
192
|
end
|
186
193
|
end
|
187
194
|
end
|
188
195
|
|
196
|
+
def enqueue_next_item(timestamp)
|
197
|
+
item = Resque.next_item_for_timestamp(timestamp)
|
198
|
+
|
199
|
+
if item
|
200
|
+
log "queuing #{item['class']} [delayed]"
|
201
|
+
enqueue(item)
|
202
|
+
end
|
203
|
+
|
204
|
+
item
|
205
|
+
end
|
206
|
+
|
189
207
|
# Enqueues all delayed jobs for a timestamp
|
190
208
|
def enqueue_delayed_items_for_timestamp(timestamp)
|
191
|
-
|
192
|
-
|
209
|
+
count = 0
|
210
|
+
batch_size = delayed_requeue_batch_size
|
211
|
+
actual_batch_size = nil
|
212
|
+
|
213
|
+
log "Processing delayed items for timestamp #{timestamp}, in batches of #{batch_size}"
|
214
|
+
|
215
|
+
loop do
|
193
216
|
handle_shutdown do
|
194
217
|
# Continually check that it is still the master
|
195
|
-
if
|
196
|
-
|
197
|
-
|
218
|
+
if am_master
|
219
|
+
actual_batch_size = enqueue_items_in_batch_for_timestamp(timestamp,
|
220
|
+
batch_size)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
count += actual_batch_size
|
225
|
+
log "queued #{count} jobs" if actual_batch_size != -1
|
226
|
+
|
227
|
+
# continue processing until there are no more items in this
|
228
|
+
# timestamp. If we don't have a full batch, this is the last one.
|
229
|
+
# This also breaks us in the event of a redis transaction failure
|
230
|
+
# i.e. enqueue_items_in_batch_for_timestamp returned -1
|
231
|
+
break if actual_batch_size < batch_size
|
232
|
+
end
|
233
|
+
|
234
|
+
log "finished queueing #{count} total jobs for timestamp #{timestamp}" if count != -1
|
235
|
+
end
|
236
|
+
|
237
|
+
def timestamp_key(timestamp)
|
238
|
+
"delayed:#{timestamp.to_i}"
|
239
|
+
end
|
240
|
+
|
241
|
+
def enqueue_items_in_batch_for_timestamp(timestamp, batch_size)
|
242
|
+
timestamp_bucket_key = timestamp_key(timestamp)
|
243
|
+
|
244
|
+
encoded_jobs_to_requeue = Resque.redis.lrange(timestamp_bucket_key, 0, batch_size - 1)
|
245
|
+
|
246
|
+
# Watch is used to ensure that the timestamp bucket we are operating on
|
247
|
+
# is not altered by any other clients between the watch call and when we call exec
|
248
|
+
# (to execute the multi block). We should error catch on the redis.exec return value
|
249
|
+
# as that will indicate if the entire transaction was aborted or not. Though we should
|
250
|
+
# be safe as our ltrim is inside the multi block and therefore also would have been
|
251
|
+
# aborted. So nothing would have been queued, but also nothing lost from the bucket.
|
252
|
+
watch_result = Resque.redis.watch(timestamp_bucket_key) do
|
253
|
+
Resque.redis.multi do |pipeline|
|
254
|
+
encoded_jobs_to_requeue.each do |encoded_job|
|
255
|
+
pipeline.srem("timestamps:#{encoded_job}", timestamp_bucket_key)
|
256
|
+
|
257
|
+
decoded_job = Resque.decode(encoded_job)
|
258
|
+
enqueue(decoded_job)
|
198
259
|
end
|
260
|
+
|
261
|
+
pipeline.ltrim(timestamp_bucket_key, batch_size, -1)
|
199
262
|
end
|
200
|
-
|
201
|
-
|
263
|
+
end
|
264
|
+
|
265
|
+
# Did the multi block successfully remove from this timestamp and enqueue the jobs?
|
266
|
+
success = !watch_result.nil?
|
267
|
+
|
268
|
+
# If this was the last batch in this timestamp bucket, clean up
|
269
|
+
if success && encoded_jobs_to_requeue.count < batch_size
|
270
|
+
Resque.clean_up_timestamp(timestamp_bucket_key, timestamp)
|
271
|
+
end
|
272
|
+
|
273
|
+
unless success
|
274
|
+
# Our batched transaction failed in Redis due to the timestamp_bucket_key value
|
275
|
+
# being modified while we built our multi block. We return -1 to ensure we break
|
276
|
+
# out of the loop iterating on this timestamp so it can be re-processed via the
|
277
|
+
# loop in handle_delayed_items.
|
278
|
+
return -1
|
279
|
+
end
|
280
|
+
|
281
|
+
# will return 0 if none were left to batch
|
282
|
+
encoded_jobs_to_requeue.count
|
283
|
+
end
|
284
|
+
|
285
|
+
def enqueue(config)
|
286
|
+
enqueue_from_config(config)
|
287
|
+
rescue => e
|
288
|
+
Resque::Scheduler.failure_handler.on_enqueue_failure(config, e)
|
202
289
|
end
|
203
290
|
|
204
291
|
def handle_shutdown
|
@@ -207,43 +294,50 @@ module Resque
|
|
207
294
|
exit if @shutdown
|
208
295
|
end
|
209
296
|
|
210
|
-
def handle_errors
|
211
|
-
begin
|
212
|
-
yield
|
213
|
-
rescue Exception => e
|
214
|
-
log! "#{e.class.name}: #{e.message}"
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
297
|
# Enqueues a job based on a config hash
|
219
298
|
def enqueue_from_config(job_config)
|
220
299
|
args = job_config['args'] || job_config[:args]
|
221
300
|
|
222
301
|
klass_name = job_config['class'] || job_config[:class]
|
223
|
-
|
302
|
+
begin
|
303
|
+
klass = Resque::Scheduler::Util.constantize(klass_name)
|
304
|
+
rescue NameError
|
305
|
+
klass = klass_name
|
306
|
+
end
|
224
307
|
|
225
308
|
params = args.is_a?(Hash) ? [args] : Array(args)
|
226
|
-
queue = job_config['queue'] ||
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
309
|
+
queue = job_config['queue'] ||
|
310
|
+
job_config[:queue] ||
|
311
|
+
Resque.queue_from_class(klass)
|
312
|
+
# Support custom job classes like those that inherit from
|
313
|
+
# Resque::JobWithStatus (resque-status)
|
314
|
+
job_klass = job_config['custom_job_class']
|
315
|
+
if job_klass && job_klass != 'Resque::Job'
|
316
|
+
# The custom job class API must offer a static "scheduled" method. If
|
317
|
+
# the custom job class can not be constantized (via a requeue call
|
318
|
+
# from the web perhaps), fall back to enqueuing normally via
|
319
|
+
# Resque::Job.create.
|
232
320
|
begin
|
233
|
-
Resque.constantize(job_klass).scheduled(
|
321
|
+
Resque::Scheduler::Util.constantize(job_klass).scheduled(
|
322
|
+
queue, klass_name, *params
|
323
|
+
)
|
234
324
|
rescue NameError
|
235
|
-
# Note that the custom job class (job_config['custom_job_class'])
|
325
|
+
# Note that the custom job class (job_config['custom_job_class'])
|
326
|
+
# is the one enqueued
|
236
327
|
Resque::Job.create(queue, job_klass, *params)
|
237
328
|
end
|
238
329
|
else
|
239
|
-
#
|
330
|
+
# Hack to avoid havoc for people shoving stuff into queues
|
240
331
|
# for non-existent classes (for example: running scheduler in
|
241
|
-
# one app that schedules for another
|
332
|
+
# one app that schedules for another.
|
242
333
|
if Class === klass
|
243
|
-
|
334
|
+
Resque::Scheduler::Plugin.run_before_delayed_enqueue_hooks(
|
335
|
+
klass, *params
|
336
|
+
)
|
244
337
|
|
245
|
-
# If the class is a custom job class, call self#scheduled on it.
|
246
|
-
#
|
338
|
+
# If the class is a custom job class, call self#scheduled on it.
|
339
|
+
# This allows you to do things like Resque.enqueue_at(timestamp,
|
340
|
+
# CustomJobClass). Otherwise, pass off to Resque.
|
247
341
|
if klass.respond_to?(:scheduled)
|
248
342
|
klass.scheduled(queue, klass_name, *params)
|
249
343
|
else
|
@@ -258,7 +352,7 @@ module Resque
|
|
258
352
|
end
|
259
353
|
|
260
354
|
def rufus_scheduler
|
261
|
-
@rufus_scheduler ||= Rufus::Scheduler.
|
355
|
+
@rufus_scheduler ||= Rufus::Scheduler.new
|
262
356
|
end
|
263
357
|
|
264
358
|
# Stops old rufus scheduler and creates a new one. Returns the new
|
@@ -266,21 +360,23 @@ module Resque
|
|
266
360
|
def clear_schedule!
|
267
361
|
rufus_scheduler.stop
|
268
362
|
@rufus_scheduler = nil
|
269
|
-
|
363
|
+
@scheduled_jobs = {}
|
270
364
|
rufus_scheduler
|
271
365
|
end
|
272
366
|
|
273
367
|
def reload_schedule!
|
274
|
-
procline
|
368
|
+
procline 'Reloading Schedule'
|
275
369
|
clear_schedule!
|
276
370
|
load_schedule!
|
277
371
|
end
|
278
372
|
|
279
373
|
def update_schedule
|
280
374
|
if Resque.redis.scard(:schedules_changed) > 0
|
281
|
-
procline
|
282
|
-
|
283
|
-
|
375
|
+
procline 'Updating schedule'
|
376
|
+
loop do
|
377
|
+
schedule_name = Resque.redis.spop(:schedules_changed)
|
378
|
+
break unless schedule_name
|
379
|
+
Resque.reload_schedule!
|
284
380
|
if Resque.schedule.keys.include?(schedule_name)
|
285
381
|
unschedule_job(schedule_name)
|
286
382
|
load_schedule_job(schedule_name, Resque.schedule[schedule_name])
|
@@ -288,7 +384,7 @@ module Resque
|
|
288
384
|
unschedule_job(schedule_name)
|
289
385
|
end
|
290
386
|
end
|
291
|
-
procline
|
387
|
+
procline 'Schedules Loaded'
|
292
388
|
end
|
293
389
|
end
|
294
390
|
|
@@ -296,44 +392,132 @@ module Resque
|
|
296
392
|
if scheduled_jobs[name]
|
297
393
|
log "Removing schedule #{name}"
|
298
394
|
scheduled_jobs[name].unschedule
|
299
|
-
|
395
|
+
@scheduled_jobs.delete(name)
|
300
396
|
end
|
301
397
|
end
|
302
398
|
|
303
399
|
# Sleeps and returns true
|
304
400
|
def poll_sleep
|
401
|
+
handle_shutdown do
|
402
|
+
begin
|
403
|
+
poll_sleep_loop
|
404
|
+
ensure
|
405
|
+
@sleeping = false
|
406
|
+
end
|
407
|
+
end
|
408
|
+
true
|
409
|
+
end
|
410
|
+
|
411
|
+
def poll_sleep_loop
|
305
412
|
@sleeping = true
|
306
|
-
|
307
|
-
|
413
|
+
if poll_sleep_amount > 0
|
414
|
+
start = Time.now
|
415
|
+
loop do
|
416
|
+
elapsed_sleep = (Time.now - start)
|
417
|
+
remaining_sleep = poll_sleep_amount - elapsed_sleep
|
418
|
+
@do_break = false
|
419
|
+
if remaining_sleep <= 0
|
420
|
+
@do_break = true
|
421
|
+
else
|
422
|
+
@do_break = handle_signals_with_operation do
|
423
|
+
sleep(remaining_sleep)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
break if @do_break
|
427
|
+
end
|
428
|
+
else
|
429
|
+
handle_signals_with_operation
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def handle_signals_with_operation
|
434
|
+
yield if block_given?
|
435
|
+
handle_signals
|
436
|
+
false
|
437
|
+
rescue Interrupt
|
438
|
+
before_shutdown if @shutdown
|
308
439
|
true
|
309
440
|
end
|
310
441
|
|
442
|
+
def stop_rufus_scheduler
|
443
|
+
rufus_scheduler.shutdown(:wait)
|
444
|
+
end
|
445
|
+
|
446
|
+
def before_shutdown
|
447
|
+
stop_rufus_scheduler
|
448
|
+
release_master_lock
|
449
|
+
end
|
450
|
+
|
311
451
|
# Sets the shutdown flag, clean schedules and exits if sleeping
|
312
452
|
def shutdown
|
453
|
+
return if @shutdown
|
313
454
|
@shutdown = true
|
314
|
-
|
315
|
-
if @sleeping
|
316
|
-
Resque.clean_schedules
|
317
|
-
Thread.new { release_master_lock! }
|
318
|
-
exit
|
319
|
-
end
|
455
|
+
log!('Shutting down')
|
456
|
+
@th.raise Interrupt if @sleeping
|
320
457
|
end
|
321
458
|
|
322
459
|
def log!(msg)
|
323
|
-
logger.info msg
|
460
|
+
logger.info { msg }
|
461
|
+
end
|
462
|
+
|
463
|
+
def log_error(msg)
|
464
|
+
logger.error { msg }
|
324
465
|
end
|
325
466
|
|
326
467
|
def log(msg)
|
327
|
-
logger.debug msg
|
468
|
+
logger.debug { msg }
|
328
469
|
end
|
329
470
|
|
330
471
|
def procline(string)
|
331
472
|
log! string
|
332
|
-
|
473
|
+
argv0 = build_procline(string)
|
474
|
+
log "Setting procline #{argv0.inspect}"
|
475
|
+
$0 = argv0
|
333
476
|
end
|
334
477
|
|
335
|
-
|
478
|
+
def failure_handler
|
479
|
+
@failure_handler ||= Resque::Scheduler::FailureHandler
|
480
|
+
end
|
336
481
|
|
337
|
-
|
482
|
+
def logger
|
483
|
+
@logger ||= Resque::Scheduler::LoggerBuilder.new(
|
484
|
+
quiet: quiet,
|
485
|
+
verbose: verbose,
|
486
|
+
log_dev: logfile,
|
487
|
+
format: logformat
|
488
|
+
).build
|
489
|
+
end
|
490
|
+
|
491
|
+
private
|
338
492
|
|
493
|
+
def enqueue_recurring(name, config)
|
494
|
+
if am_master
|
495
|
+
log! "queueing #{config['class']} (#{name})"
|
496
|
+
enqueue(config)
|
497
|
+
Resque.last_enqueued_at(name, Time.now.to_s)
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
def app_str
|
502
|
+
app_name ? "[#{app_name}]" : ''
|
503
|
+
end
|
504
|
+
|
505
|
+
def env_str
|
506
|
+
env ? "[#{env}]" : ''
|
507
|
+
end
|
508
|
+
|
509
|
+
def build_procline(string)
|
510
|
+
"#{internal_name}#{app_str}#{env_str}: #{string}"
|
511
|
+
end
|
512
|
+
|
513
|
+
def internal_name
|
514
|
+
"resque-scheduler-#{Resque::Scheduler::VERSION}"
|
515
|
+
end
|
516
|
+
|
517
|
+
def am_master
|
518
|
+
@am_master = master? unless defined?(@am_master)
|
519
|
+
@am_master
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
339
523
|
end
|