resque-scheduler 4.2.1 → 4.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of resque-scheduler might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +12 -0
- data/.github/workflows/rubocop.yml +27 -0
- data/.github/workflows/ruby.yml +48 -0
- data/AUTHORS.md +7 -0
- data/CHANGELOG.md +495 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/README.md +37 -17
- data/Rakefile +1 -5
- data/{bin → exe}/resque-scheduler +0 -0
- data/lib/resque/scheduler/cli.rb +1 -0
- data/lib/resque/scheduler/delaying_extensions.rb +38 -6
- data/lib/resque/scheduler/env.rb +8 -4
- data/lib/resque/scheduler/lock/resilient.rb +19 -12
- data/lib/resque/scheduler/locking.rb +2 -2
- data/lib/resque/scheduler/scheduling_extensions.rb +4 -3
- data/lib/resque/scheduler/server/views/delayed.erb +1 -1
- data/lib/resque/scheduler/server/views/search_form.erb +1 -1
- data/lib/resque/scheduler/server.rb +1 -1
- data/lib/resque/scheduler/version.rb +1 -1
- data/lib/resque/scheduler.rb +39 -16
- data/resque-scheduler.gemspec +29 -10
- metadata +62 -66
- data/.gitignore +0 -17
- data/.rubocop.yml +0 -18
- data/.rubocop_todo.yml +0 -71
- data/.simplecov +0 -3
- data/.travis.yml +0 -26
- data/.vagrant-provision-as-vagrant.sh +0 -15
- data/.vagrant-provision.sh +0 -23
- data/.vagrant-skel/bash_profile +0 -7
- data/.vagrant-skel/bashrc +0 -7
- data/HISTORY.md +0 -303
- data/Vagrantfile +0 -14
- data/examples/Rakefile +0 -2
- data/examples/config/initializers/resque-web.rb +0 -37
- data/examples/dynamic-scheduling/README.md +0 -28
- data/examples/dynamic-scheduling/app/jobs/fix_schedules_job.rb +0 -52
- data/examples/dynamic-scheduling/app/jobs/send_email_job.rb +0 -9
- data/examples/dynamic-scheduling/app/models/user.rb +0 -16
- data/examples/dynamic-scheduling/config/resque.yml +0 -4
- data/examples/dynamic-scheduling/config/static_schedule.yml +0 -7
- data/examples/dynamic-scheduling/lib/tasks/resque.rake +0 -48
- data/examples/run-resque-web +0 -3
- data/script/migrate_to_timestamps_set.rb +0 -16
- data/tasks/resque_scheduler.rake +0 -2
- data/test/cli_test.rb +0 -231
- data/test/delayed_queue_test.rb +0 -925
- data/test/env_test.rb +0 -47
- data/test/multi_process_test.rb +0 -125
- data/test/resque-web_test.rb +0 -364
- data/test/scheduler_args_test.rb +0 -222
- data/test/scheduler_hooks_test.rb +0 -55
- data/test/scheduler_locking_test.rb +0 -316
- data/test/scheduler_setup_test.rb +0 -141
- data/test/scheduler_task_test.rb +0 -72
- data/test/scheduler_test.rb +0 -473
- data/test/test_helper.rb +0 -147
- data/test/util_test.rb +0 -17
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at resque@librelist.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
resque-scheduler
|
2
2
|
================
|
3
3
|
|
4
|
-
|
5
|
-
[![Gem Version](https://badge.fury.io/rb/resque-scheduler.
|
6
|
-
[![
|
7
|
-
[![Code Climate](https://codeclimate.com/github/resque/resque-scheduler.
|
4
|
+
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/resque-scheduler.svg)](https://badge.fury.io/rb/resque-scheduler)
|
6
|
+
[![Ruby specs](https://github.com/resque/resque-scheduler/actions/workflows/ruby.yml/badge.svg)](https://github.com/resque/resque-scheduler/actions)
|
7
|
+
[![Code Climate](https://codeclimate.com/github/resque/resque-scheduler/badges/gpa.svg)](https://codeclimate.com/github/resque/resque-scheduler)
|
8
8
|
|
9
9
|
### Description
|
10
10
|
|
11
11
|
Resque-scheduler is an extension to [Resque](http://github.com/resque/resque)
|
12
12
|
that adds support for queueing items in the future.
|
13
13
|
|
14
|
-
Job scheduling is supported in two different
|
14
|
+
Job scheduling is supported in two different ways: Recurring (scheduled) and
|
15
15
|
Delayed.
|
16
16
|
|
17
17
|
Scheduled jobs are like cron jobs, recurring on a regular basis. Delayed
|
@@ -158,7 +158,7 @@ following task to wherever tasks are kept, such as
|
|
158
158
|
```ruby
|
159
159
|
task 'resque:pool:setup' do
|
160
160
|
Resque::Pool.after_prefork do |job|
|
161
|
-
Resque.redis.
|
161
|
+
Resque.redis._client.reconnect
|
162
162
|
end
|
163
163
|
end
|
164
164
|
```
|
@@ -233,6 +233,17 @@ Resque.remove_delayed_selection { |args| args[0]['account_id'] == current_accoun
|
|
233
233
|
Resque.remove_delayed_selection { |args| args[0]['user_id'] == current_user.id }
|
234
234
|
```
|
235
235
|
|
236
|
+
If you need to cancel a delayed job based on some matching arguments AND by which class the job is, but don't wish to specify each argument from when the job was created, you can do like so:
|
237
|
+
|
238
|
+
``` ruby
|
239
|
+
# after you've enqueued a job like:
|
240
|
+
Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :account_id => current_account.id, :user_id => current_user.id)
|
241
|
+
# remove jobs matching just the account and that were of the class SendFollowUpEmail:
|
242
|
+
Resque.remove_delayed_selection(SendFollowUpEmail) { |args| args[0]['account_id'] == current_account.id }
|
243
|
+
# or remove jobs matching just the user and that were of the class SendFollowUpEmail:
|
244
|
+
Resque.remove_delayed_selection(SendFollowUpEmail) { |args| args[0]['user_id'] == current_user.id }
|
245
|
+
```
|
246
|
+
|
236
247
|
If you need to enqueue immediately a delayed job based on some matching arguments, but don't wish to specify each argument from when the job was created, you can do like so:
|
237
248
|
|
238
249
|
``` ruby
|
@@ -289,6 +300,19 @@ clear_leaderboards_contributors:
|
|
289
300
|
description: "This job resets the weekly leaderboard for contributions"
|
290
301
|
```
|
291
302
|
|
303
|
+
If you would like to setup a job that is executed manually you can configure like this in your YAML file.
|
304
|
+
|
305
|
+
```yaml
|
306
|
+
ImportOrdersManual:
|
307
|
+
description: 'Import Amazon Orders Manual'
|
308
|
+
custom_job_class: 'AmazonMws::ImportOrdersJob'
|
309
|
+
never: "* * * * *"
|
310
|
+
queue: high
|
311
|
+
description: "This is a manual job for importing orders."
|
312
|
+
args:
|
313
|
+
days_in_arrears: 7
|
314
|
+
```
|
315
|
+
|
292
316
|
The queue value is optional, but if left unspecified resque-scheduler will
|
293
317
|
attempt to get the queue from the job class, which means it needs to be
|
294
318
|
defined. If you're getting "uninitialized constant" errors, you probably
|
@@ -323,8 +347,8 @@ for handling the heavy lifting of the actual scheduling engine.
|
|
323
347
|
#### Dynamic schedules
|
324
348
|
|
325
349
|
Dynamic schedules are programmatically set on a running `resque-scheduler`.
|
326
|
-
|
327
|
-
when setting schedules.
|
350
|
+
Most [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler) options are supported
|
351
|
+
when setting schedules. Specifically the `overlap` option will not work.
|
328
352
|
|
329
353
|
Dynamic schedules are not enabled by default. To be able to dynamically set schedules, you
|
330
354
|
must pass the following to `resque-scheduler` initialization (see *Installation* above for a more complete example):
|
@@ -501,7 +525,7 @@ RESQUE_SCHEDULER_MASTER_LOCK_PREFIX=MyApp: rake resque:scheduler
|
|
501
525
|
|
502
526
|
### resque-web Additions
|
503
527
|
|
504
|
-
Resque-scheduler also adds
|
528
|
+
Resque-scheduler also adds two tabs to the resque-web UI. One is for viewing
|
505
529
|
(and manually queueing) the schedule and one is for viewing pending jobs in
|
506
530
|
the delayed queue.
|
507
531
|
|
@@ -535,11 +559,7 @@ require 'resque/scheduler/server'
|
|
535
559
|
|
536
560
|
That should make the scheduler tabs show up in `resque-web`.
|
537
561
|
|
538
|
-
|
539
|
-
|
540
|
-
As of resque-scheduler 2.0.0, it's no longer necessary to have the resque-web
|
541
|
-
process aware of the schedule because it reads it from redis. But prior to
|
542
|
-
2.0, you'll want to make sure you load the schedule in this file as well.
|
562
|
+
You'll want to make sure you load the schedule in this file as well.
|
543
563
|
Something like this:
|
544
564
|
|
545
565
|
```ruby
|
@@ -608,18 +628,18 @@ with resque could easily work on resque-scheduler.
|
|
608
628
|
|
609
629
|
Working on resque-scheduler requires the following:
|
610
630
|
|
611
|
-
* A relatively modern Ruby interpreter
|
631
|
+
* A relatively modern Ruby interpreter
|
612
632
|
* bundler
|
613
633
|
|
614
634
|
The development setup looks like this, which is roughly the same thing
|
615
|
-
that happens on Travis CI:
|
635
|
+
that happens on Travis CI and Appveyor:
|
616
636
|
|
617
637
|
``` bash
|
618
638
|
# Install everything
|
619
639
|
bundle install
|
620
640
|
|
621
641
|
# Make sure tests are green before you change stuff
|
622
|
-
bundle exec rake
|
642
|
+
bundle exec rubocop && bundle exec rake
|
623
643
|
# Change stuff
|
624
644
|
# Repeat
|
625
645
|
```
|
data/Rakefile
CHANGED
@@ -1,13 +1,9 @@
|
|
1
1
|
# vim:fileencoding=utf-8
|
2
2
|
require 'bundler/gem_tasks'
|
3
3
|
require 'rake/testtask'
|
4
|
-
require 'rubocop/rake_task'
|
5
4
|
require 'yard'
|
6
5
|
|
7
|
-
task default:
|
8
|
-
task default: [:test] if RUBY_PLATFORM =~ /java/
|
9
|
-
|
10
|
-
RuboCop::RakeTask.new
|
6
|
+
task default: :test
|
11
7
|
|
12
8
|
Rake::TestTask.new do |t|
|
13
9
|
t.libs << 'test'
|
File without changes
|
data/lib/resque/scheduler/cli.rb
CHANGED
@@ -24,7 +24,7 @@ module Resque
|
|
24
24
|
def enqueue_at_with_queue(queue, timestamp, klass, *args)
|
25
25
|
return false unless plugin.run_before_schedule_hooks(klass, *args)
|
26
26
|
|
27
|
-
if Resque.inline? || timestamp.to_i
|
27
|
+
if Resque.inline? || timestamp.to_i <= Time.now.to_i
|
28
28
|
# Just create the job and let resque perform it right away with
|
29
29
|
# inline. If the class is a custom job class, call self#scheduled
|
30
30
|
# on it. This allows you to do things like
|
@@ -33,7 +33,7 @@ module Resque
|
|
33
33
|
if klass.respond_to?(:scheduled)
|
34
34
|
klass.scheduled(queue, klass.to_s, *args)
|
35
35
|
else
|
36
|
-
Resque
|
36
|
+
Resque.enqueue_to(queue, klass, *args)
|
37
37
|
end
|
38
38
|
else
|
39
39
|
delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
|
@@ -45,6 +45,9 @@ module Resque
|
|
45
45
|
# Identical to enqueue_at but takes number_of_seconds_from_now
|
46
46
|
# instead of a timestamp.
|
47
47
|
def enqueue_in(number_of_seconds_from_now, klass, *args)
|
48
|
+
unless number_of_seconds_from_now.is_a?(Numeric)
|
49
|
+
raise ArgumentError, 'Please supply a numeric number of seconds'
|
50
|
+
end
|
48
51
|
enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
|
49
52
|
end
|
50
53
|
|
@@ -53,14 +56,17 @@ module Resque
|
|
53
56
|
# number of seconds has passed.
|
54
57
|
def enqueue_in_with_queue(queue, number_of_seconds_from_now,
|
55
58
|
klass, *args)
|
59
|
+
unless number_of_seconds_from_now.is_a?(Numeric)
|
60
|
+
raise ArgumentError, 'Please supply a numeric number of seconds'
|
61
|
+
end
|
56
62
|
enqueue_at_with_queue(queue, Time.now + number_of_seconds_from_now,
|
57
63
|
klass, *args)
|
58
64
|
end
|
59
65
|
|
60
66
|
# Used internally to stuff the item into the schedule sorted list.
|
61
|
-
# +timestamp+ can be either in seconds or a datetime object
|
62
|
-
#
|
63
|
-
# that time, else false
|
67
|
+
# +timestamp+ can be either in seconds or a datetime object. The
|
68
|
+
# insertion time complexity is O(log(n)). Returns true if it's
|
69
|
+
# the first job to be scheduled at that time, else false.
|
64
70
|
def delayed_push(timestamp, item)
|
65
71
|
# First add this item to the list for this timestamp
|
66
72
|
redis.rpush("delayed:#{timestamp.to_i}", encode(item))
|
@@ -82,6 +88,7 @@ module Resque
|
|
82
88
|
end
|
83
89
|
|
84
90
|
# Returns the size of the delayed queue schedule
|
91
|
+
# this does not represent the number of items in the queue to be scheduled
|
85
92
|
def delayed_queue_schedule_size
|
86
93
|
redis.zcard :delayed_queue_schedule
|
87
94
|
end
|
@@ -143,6 +150,11 @@ module Resque
|
|
143
150
|
remove_delayed_job(search)
|
144
151
|
end
|
145
152
|
|
153
|
+
def remove_delayed_in_queue(klass, queue, *args)
|
154
|
+
search = encode(job_to_hash_with_queue(queue, klass, args))
|
155
|
+
remove_delayed_job(search)
|
156
|
+
end
|
157
|
+
|
146
158
|
# Given an encoded item, enqueue it now
|
147
159
|
def enqueue_delayed(klass, *args)
|
148
160
|
hash = job_to_hash(klass, args)
|
@@ -151,6 +163,13 @@ module Resque
|
|
151
163
|
end
|
152
164
|
end
|
153
165
|
|
166
|
+
def enqueue_delayed_with_queue(klass, queue, *args)
|
167
|
+
hash = job_to_hash_with_queue(queue, klass, args)
|
168
|
+
remove_delayed_in_queue(klass, queue, *args).times do
|
169
|
+
Resque::Scheduler.enqueue_from_config(hash)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
154
173
|
# Given a block, remove jobs that return true from a block
|
155
174
|
#
|
156
175
|
# This allows for removal of delayed jobs that have arguments matching
|
@@ -175,7 +194,15 @@ module Resque
|
|
175
194
|
found_jobs.reduce(0) do |sum, encoded_job|
|
176
195
|
decoded_job = decode(encoded_job)
|
177
196
|
klass = Util.constantize(decoded_job['class'])
|
178
|
-
|
197
|
+
queue = decoded_job['queue']
|
198
|
+
|
199
|
+
if queue
|
200
|
+
jobs_queued = enqueue_delayed_with_queue(klass, queue, *decoded_job['args'])
|
201
|
+
else
|
202
|
+
jobs_queued = enqueue_delayed(klass, *decoded_job['args'])
|
203
|
+
end
|
204
|
+
|
205
|
+
jobs_queued + sum
|
179
206
|
end
|
180
207
|
end
|
181
208
|
|
@@ -265,6 +292,8 @@ module Resque
|
|
265
292
|
{ class: klass.to_s, args: args, queue: queue }
|
266
293
|
end
|
267
294
|
|
295
|
+
# Removes a job from the queue, but not modify the timestamp schedule. This method
|
296
|
+
# will not effect the output of `delayed_queue_schedule_size`
|
268
297
|
def remove_delayed_job(encoded_job)
|
269
298
|
return 0 if Resque.inline?
|
270
299
|
|
@@ -277,6 +306,9 @@ module Resque
|
|
277
306
|
end
|
278
307
|
end
|
279
308
|
|
309
|
+
# timestamp key is not removed from the schedule, this is done later
|
310
|
+
# by the scheduler loop
|
311
|
+
|
280
312
|
return 0 if replies.nil? || replies.empty?
|
281
313
|
replies.each_slice(2).map(&:first).inject(:+)
|
282
314
|
end
|
data/lib/resque/scheduler/env.rb
CHANGED
@@ -32,14 +32,18 @@ module Resque
|
|
32
32
|
|
33
33
|
# Need to set this here for conditional Process.daemon redirect of
|
34
34
|
# stderr/stdout to /dev/null
|
35
|
-
Resque::Scheduler.quiet =
|
35
|
+
Resque::Scheduler.quiet = if options.key?(:quiet)
|
36
|
+
!!options[:quiet]
|
37
|
+
else
|
38
|
+
true
|
39
|
+
end
|
36
40
|
|
37
41
|
unless Process.respond_to?('daemon')
|
38
42
|
abort 'background option is set, which requires ruby >= 1.9'
|
39
43
|
end
|
40
44
|
|
41
45
|
Process.daemon(true, !Resque::Scheduler.quiet)
|
42
|
-
Resque.redis.
|
46
|
+
Resque.redis._client.reconnect
|
43
47
|
end
|
44
48
|
|
45
49
|
def setup_pid_file
|
@@ -60,13 +64,13 @@ module Resque
|
|
60
64
|
|
61
65
|
c.dynamic = !!options[:dynamic] if options.key?(:dynamic)
|
62
66
|
|
63
|
-
c.env = options[:env] if options.key(:env)
|
67
|
+
c.env = options[:env] if options.key?(:env)
|
64
68
|
|
65
69
|
c.logfile = options[:logfile] if options.key?(:logfile)
|
66
70
|
|
67
71
|
c.logformat = options[:logformat] if options.key?(:logformat)
|
68
72
|
|
69
|
-
if psleep = options[:poll_sleep_amount] && !psleep.nil?
|
73
|
+
if (psleep = options[:poll_sleep_amount]) && !psleep.nil?
|
70
74
|
c.poll_sleep_amount = Float(psleep)
|
71
75
|
end
|
72
76
|
|
@@ -6,19 +6,11 @@ module Resque
|
|
6
6
|
module Lock
|
7
7
|
class Resilient < Base
|
8
8
|
def acquire!
|
9
|
-
|
10
|
-
acquire_sha,
|
11
|
-
keys: [key],
|
12
|
-
argv: [value]
|
13
|
-
).to_i == 1
|
9
|
+
evalsha(:acquire, [key], [value]).to_i == 1
|
14
10
|
end
|
15
11
|
|
16
12
|
def locked?
|
17
|
-
|
18
|
-
locked_sha,
|
19
|
-
keys: [key],
|
20
|
-
argv: [value]
|
21
|
-
).to_i == 1
|
13
|
+
evalsha(:locked, [key], [value]).to_i == 1
|
22
14
|
end
|
23
15
|
|
24
16
|
def timeout=(seconds)
|
@@ -32,11 +24,26 @@ module Resque
|
|
32
24
|
|
33
25
|
private
|
34
26
|
|
27
|
+
def evalsha(script, keys, argv, refresh: false)
|
28
|
+
sha_method_name = "#{script}_sha"
|
29
|
+
Resque.redis.evalsha(
|
30
|
+
send(sha_method_name, refresh),
|
31
|
+
keys: keys,
|
32
|
+
argv: argv
|
33
|
+
)
|
34
|
+
rescue Redis::CommandError => e
|
35
|
+
if e.message =~ /NOSCRIPT/
|
36
|
+
refresh = true
|
37
|
+
retry
|
38
|
+
end
|
39
|
+
raise
|
40
|
+
end
|
41
|
+
|
35
42
|
def locked_sha(refresh = false)
|
36
43
|
@locked_sha = nil if refresh
|
37
44
|
|
38
45
|
@locked_sha ||=
|
39
|
-
Resque.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
|
46
|
+
Resque.data_store.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
|
40
47
|
if redis.call('GET', KEYS[1]) == ARGV[1]
|
41
48
|
then
|
42
49
|
redis.call('EXPIRE', KEYS[1], #{timeout})
|
@@ -55,7 +62,7 @@ module Resque
|
|
55
62
|
@acquire_sha = nil if refresh
|
56
63
|
|
57
64
|
@acquire_sha ||=
|
58
|
-
Resque.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
|
65
|
+
Resque.data_store.redis.script(:load, <<-EOF.gsub(/^ {14}/, ''))
|
59
66
|
if redis.call('SETNX', KEYS[1], ARGV[1]) == 1
|
60
67
|
then
|
61
68
|
redis.call('EXPIRE', KEYS[1], #{timeout})
|
@@ -76,7 +76,7 @@ module Resque
|
|
76
76
|
|
77
77
|
def release_master_lock
|
78
78
|
master_lock.release
|
79
|
-
rescue
|
79
|
+
rescue *INTERMITTENT_ERRORS
|
80
80
|
@master_lock = nil
|
81
81
|
end
|
82
82
|
|
@@ -97,7 +97,7 @@ module Resque
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def redis_master_version
|
100
|
-
Resque.redis.info['redis_version'].to_f
|
100
|
+
Resque.data_store.redis.info['redis_version'].to_f
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
@@ -36,7 +36,7 @@ module Resque
|
|
36
36
|
# :args can be any yaml which will be converted to a ruby literal and
|
37
37
|
# passed in a params. (optional)
|
38
38
|
#
|
39
|
-
# :
|
39
|
+
# :rails_env is the list of envs where the job gets loaded. Envs are
|
40
40
|
# comma separated (optional)
|
41
41
|
#
|
42
42
|
# :description is just that, a description of the job (optional). If
|
@@ -101,12 +101,13 @@ module Resque
|
|
101
101
|
end
|
102
102
|
|
103
103
|
# remove a given schedule by name
|
104
|
-
|
104
|
+
# Preventing a reload is optional and available to batch operations
|
105
|
+
def remove_schedule(name, reload = true)
|
105
106
|
non_persistent_schedules.delete(name)
|
106
107
|
redis.hdel(:persistent_schedules, name)
|
107
108
|
redis.sadd(:schedules_changed, name)
|
108
109
|
|
109
|
-
reload_schedule!
|
110
|
+
reload_schedule! if reload
|
110
111
|
end
|
111
112
|
|
112
113
|
private
|
@@ -46,7 +46,7 @@
|
|
46
46
|
<td><%= h(show_job_arguments(job['args'])) if job && delayed_timestamp_size == 1 %></td>
|
47
47
|
<td>
|
48
48
|
<% if job %>
|
49
|
-
<a href="<%=u URI("/delayed/jobs/#{job['class']}?args=" +
|
49
|
+
<a href="<%=u URI("/delayed/jobs/#{CGI.escape(job['class'])}?args=" + CGI.escape(job['args'].to_json)) %>">All schedules</a>
|
50
50
|
<% end %>
|
51
51
|
</td>
|
52
52
|
</tr>
|
@@ -87,7 +87,7 @@ module Resque
|
|
87
87
|
def delayed_jobs_klass
|
88
88
|
begin
|
89
89
|
klass = Resque::Scheduler::Util.constantize(params[:klass])
|
90
|
-
@args = JSON.load(
|
90
|
+
@args = JSON.load(CGI.unescape(params[:args]))
|
91
91
|
@timestamps = Resque.scheduled_at(klass, *@args)
|
92
92
|
rescue
|
93
93
|
@timestamps = []
|
data/lib/resque/scheduler.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# vim:fileencoding=utf-8
|
2
2
|
|
3
|
+
require 'redis/errors'
|
3
4
|
require 'rufus/scheduler'
|
4
5
|
require_relative 'scheduler/configuration'
|
5
6
|
require_relative 'scheduler/locking'
|
@@ -13,6 +14,9 @@ module Resque
|
|
13
14
|
autoload :Extension, 'resque/scheduler/extension'
|
14
15
|
autoload :Util, 'resque/scheduler/util'
|
15
16
|
autoload :VERSION, 'resque/scheduler/version'
|
17
|
+
INTERMITTENT_ERRORS = [
|
18
|
+
Errno::EAGAIN, Errno::ECONNRESET, Redis::CannotConnectError, Redis::TimeoutError
|
19
|
+
].freeze
|
16
20
|
|
17
21
|
private
|
18
22
|
|
@@ -44,13 +48,7 @@ module Resque
|
|
44
48
|
$stdout.sync = true
|
45
49
|
$stderr.sync = true
|
46
50
|
|
47
|
-
|
48
|
-
# If dynamic is set, load that schedule otherwise use normal load
|
49
|
-
if dynamic
|
50
|
-
reload_schedule!
|
51
|
-
else
|
52
|
-
load_schedule!
|
53
|
-
end
|
51
|
+
was_master = nil
|
54
52
|
|
55
53
|
begin
|
56
54
|
@th = Thread.current
|
@@ -58,11 +56,21 @@ module Resque
|
|
58
56
|
# Now start the scheduling part of the loop.
|
59
57
|
loop do
|
60
58
|
begin
|
61
|
-
|
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'
|
63
|
+
|
64
|
+
# Load schedule because changed
|
65
|
+
reload_schedule!
|
66
|
+
end
|
67
|
+
|
68
|
+
if am_master
|
62
69
|
handle_delayed_items
|
63
70
|
update_schedule if dynamic
|
64
71
|
end
|
65
|
-
|
72
|
+
was_master = am_master
|
73
|
+
rescue *INTERMITTENT_ERRORS => e
|
66
74
|
log! e.message
|
67
75
|
release_master_lock
|
68
76
|
end
|
@@ -99,7 +107,7 @@ module Resque
|
|
99
107
|
Resque.schedule.each do |name, config|
|
100
108
|
load_schedule_job(name, config)
|
101
109
|
end
|
102
|
-
Resque.redis.del(:schedules_changed)
|
110
|
+
Resque.redis.del(:schedules_changed) if am_master && dynamic
|
103
111
|
procline 'Schedules Loaded'
|
104
112
|
end
|
105
113
|
|
@@ -141,11 +149,7 @@ module Resque
|
|
141
149
|
args = [args, nil, job: true] if args.is_a?(::String)
|
142
150
|
|
143
151
|
job = rufus_scheduler.send(interval_type, *args) do
|
144
|
-
|
145
|
-
log! "queueing #{config['class']} (#{name})"
|
146
|
-
Resque.last_enqueued_at(name, Time.now.to_s)
|
147
|
-
enqueue(config)
|
148
|
-
end
|
152
|
+
enqueue_recurring(name, config)
|
149
153
|
end
|
150
154
|
@scheduled_jobs[name] = job
|
151
155
|
interval_defined = true
|
@@ -206,7 +210,7 @@ module Resque
|
|
206
210
|
loop do
|
207
211
|
handle_shutdown do
|
208
212
|
# Continually check that it is still the master
|
209
|
-
item = enqueue_next_item(timestamp) if
|
213
|
+
item = enqueue_next_item(timestamp) if am_master
|
210
214
|
end
|
211
215
|
# continue processing until there are no more ready items in this
|
212
216
|
# timestamp
|
@@ -371,7 +375,13 @@ module Resque
|
|
371
375
|
true
|
372
376
|
end
|
373
377
|
|
378
|
+
def stop_rufus_scheduler
|
379
|
+
rufus_scheduler.shutdown(:wait)
|
380
|
+
rufus_scheduler.join
|
381
|
+
end
|
382
|
+
|
374
383
|
def before_shutdown
|
384
|
+
stop_rufus_scheduler
|
375
385
|
release_master_lock
|
376
386
|
end
|
377
387
|
|
@@ -417,6 +427,14 @@ module Resque
|
|
417
427
|
|
418
428
|
private
|
419
429
|
|
430
|
+
def enqueue_recurring(name, config)
|
431
|
+
if am_master
|
432
|
+
log! "queueing #{config['class']} (#{name})"
|
433
|
+
enqueue(config)
|
434
|
+
Resque.last_enqueued_at(name, Time.now.to_s)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
420
438
|
def app_str
|
421
439
|
app_name ? "[#{app_name}]" : ''
|
422
440
|
end
|
@@ -432,6 +450,11 @@ module Resque
|
|
432
450
|
def internal_name
|
433
451
|
"resque-scheduler-#{Resque::Scheduler::VERSION}"
|
434
452
|
end
|
453
|
+
|
454
|
+
def am_master
|
455
|
+
@am_master = master? unless defined?(@am_master)
|
456
|
+
@am_master
|
457
|
+
end
|
435
458
|
end
|
436
459
|
end
|
437
460
|
end
|