resque-scheduler 2.0.0.g → 2.0.0.h
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.
- data/HISTORY.md +2 -2
- data/README.markdown +29 -6
- data/lib/resque/scheduler.rb +18 -1
- data/lib/resque_scheduler.rb +30 -22
- data/lib/resque_scheduler/plugin.rb +25 -0
- data/lib/resque_scheduler/version.rb +1 -1
- data/test/scheduler_hooks_test.rb +13 -42
- data/test/scheduler_test.rb +39 -1
- metadata +13 -12
data/HISTORY.md
CHANGED
data/README.markdown
CHANGED
@@ -128,12 +128,6 @@ down when a particular job is supposed to be queue, they will simply "catch up"
|
|
128
128
|
once they are started again. Jobs are guaranteed to run (provided they make it
|
129
129
|
into the delayed queue) after their given queue_at time has passed.
|
130
130
|
|
131
|
-
Similar to `before_enqueue` and `after_enqueue` hooks provided in Resque
|
132
|
-
(>= 1.19.1), your jobs can specify one or more `before_schedule` and
|
133
|
-
`after_schedule` hooks, to be run before or after scheduling. If *any* of your
|
134
|
-
`before_schedule` hooks returns `false`, the job will *not* be scheduled and
|
135
|
-
your `after_schedule` hooks will *not* be run.
|
136
|
-
|
137
131
|
One other thing to note is that insertion into the delayed queue is O(log(n))
|
138
132
|
since the jobs are stored in a redis sorted set (zset). I can't imagine this
|
139
133
|
being an issue for someone since redis is stupidly fast even at log(n), but full
|
@@ -158,10 +152,15 @@ The schedule is a list of Resque worker classes with arguments and a
|
|
158
152
|
schedule frequency (in crontab syntax). The schedule is just a hash, but
|
159
153
|
is most likely stored in a YAML like so:
|
160
154
|
|
155
|
+
CancelAbandonedOrders:
|
156
|
+
cron: "*/5 * * * *"
|
157
|
+
|
161
158
|
queue_documents_for_indexing:
|
162
159
|
cron: "0 0 * * *"
|
163
160
|
# you can use rufus-scheduler "every" syntax in place of cron if you prefer
|
164
161
|
# every: 1hr
|
162
|
+
# By default the job name (hash key) will be taken as worker class name.
|
163
|
+
# If you want to have a different job name and class name, provide the 'class' option
|
165
164
|
class: QueueDocuments
|
166
165
|
queue: high
|
167
166
|
args:
|
@@ -180,6 +179,15 @@ defined. If you're getting "uninitialized constant" errors, you probably
|
|
180
179
|
need to either set the queue in the schedule or require your jobs in your
|
181
180
|
"resque:setup" rake task.
|
182
181
|
|
182
|
+
You can provide options to "every" or "cron" via Array:
|
183
|
+
|
184
|
+
clear_leaderboards_moderator:
|
185
|
+
every: ["30s", :first_in => '120s']
|
186
|
+
class: CheckDaemon
|
187
|
+
queue: daemons
|
188
|
+
description: "This job will check Daemon every 30 seconds after 120 seconds after start"
|
189
|
+
|
190
|
+
|
183
191
|
NOTE: Six parameter cron's are also supported (as they supported by
|
184
192
|
rufus-scheduler which powers the resque-scheduler process). This allows you
|
185
193
|
to schedule jobs per second (ie: "30 * * * * *" would fire a job every 30
|
@@ -206,6 +214,21 @@ from the `config.time_zone` value, make sure it's the right format, e.g. with:
|
|
206
214
|
|
207
215
|
A future version of resque-scheduler may do this for you.
|
208
216
|
|
217
|
+
#### Hooks
|
218
|
+
|
219
|
+
Similar to the `before_enqueue`- and `after_enqueue`-hooks provided in Resque
|
220
|
+
(>= 1.19.1), your jobs can specify one or more of the following hooks:
|
221
|
+
|
222
|
+
* `before_schedule`: Called with the job args before a job is placed on
|
223
|
+
the delayed queue. If the hook returns `false`, the job will not be placed on
|
224
|
+
the queue.
|
225
|
+
* `after_schedule`: Called with the job args after a job is placed on the
|
226
|
+
delayed queue. Any exception raised propagates up to the code with queued the
|
227
|
+
job.
|
228
|
+
* `before_delayed_enqueue`: Called with the job args after the job has been
|
229
|
+
removed from the delayed queue, but not yet put on a normal queue. It is
|
230
|
+
called before `before_enqueue`-hooks, and on the same job instance as the
|
231
|
+
`before_enqueue`-hooks will be invoked on. Return values are ignored.
|
209
232
|
|
210
233
|
#### Support for resque-status (and other custom jobs)
|
211
234
|
|
data/lib/resque/scheduler.rb
CHANGED
@@ -103,6 +103,21 @@ module Resque
|
|
103
103
|
Resque.redis.del(:schedules_changed)
|
104
104
|
procline "Schedules Loaded"
|
105
105
|
end
|
106
|
+
|
107
|
+
# modify interval type value to value with options if options available
|
108
|
+
def optionizate_interval_value(value)
|
109
|
+
args = value
|
110
|
+
if args.is_a?(::Array)
|
111
|
+
return args.first if args.size > 2 || !args.last.is_a?(::Hash)
|
112
|
+
# symbolize keys of hash for options
|
113
|
+
args[1] = args[1].inject({}) do |m, i|
|
114
|
+
key, value = i
|
115
|
+
m[(key.to_sym rescue key) || key] = value
|
116
|
+
m
|
117
|
+
end
|
118
|
+
end
|
119
|
+
args
|
120
|
+
end
|
106
121
|
|
107
122
|
# Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
|
108
123
|
def load_schedule_job(name, config)
|
@@ -116,7 +131,8 @@ module Resque
|
|
116
131
|
interval_types = %w{cron every}
|
117
132
|
interval_types.each do |interval_type|
|
118
133
|
if !config[interval_type].nil? && config[interval_type].length > 0
|
119
|
-
|
134
|
+
args = optionizate_interval_value(config[interval_type])
|
135
|
+
@@scheduled_jobs[name] = rufus_scheduler.send(interval_type, *args) do
|
120
136
|
log! "queueing #{config['class']} (#{name})"
|
121
137
|
handle_errors { enqueue_from_config(config) }
|
122
138
|
end
|
@@ -202,6 +218,7 @@ module Resque
|
|
202
218
|
# for non-existent classes (for example: running scheduler in
|
203
219
|
# one app that schedules for another
|
204
220
|
if Class === klass
|
221
|
+
ResqueScheduler::Plugin.run_before_delayed_enqueue_hooks(klass, *params)
|
205
222
|
Resque.enqueue(klass, *params)
|
206
223
|
else
|
207
224
|
# This will not run the before_hooks in rescue, but will at least
|
data/lib/resque_scheduler.rb
CHANGED
@@ -4,27 +4,36 @@ require 'resque/server'
|
|
4
4
|
require 'resque_scheduler/version'
|
5
5
|
require 'resque/scheduler'
|
6
6
|
require 'resque_scheduler/server'
|
7
|
+
require 'resque_scheduler/plugin'
|
7
8
|
|
8
9
|
module ResqueScheduler
|
9
10
|
|
10
11
|
#
|
11
12
|
# Accepts a new schedule configuration of the form:
|
12
13
|
#
|
13
|
-
# {
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
14
|
+
# {
|
15
|
+
# "MakeTea" => {
|
16
|
+
# "every" => "1m" },
|
17
|
+
# "some_name" => {
|
18
|
+
# "cron" => "5/* * * *",
|
19
|
+
# "class" => "DoSomeWork",
|
20
|
+
# "args" => "work on this string",
|
21
|
+
# "description" => "this thing works it"s butter off" },
|
22
|
+
# ...
|
23
|
+
# }
|
18
24
|
#
|
19
|
-
#
|
20
|
-
# the scheduled job
|
25
|
+
# Hash keys can be anything and are used to describe and reference
|
26
|
+
# the scheduled job. If the "class" argument is missing, the key
|
27
|
+
# is used implicitly as "class" argument - in the "MakeTea" example,
|
28
|
+
# "MakeTea" is used both as job name and resque worker class.
|
21
29
|
#
|
22
|
-
# :cron can be any cron scheduling string
|
30
|
+
# :cron can be any cron scheduling string
|
23
31
|
#
|
24
32
|
# :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage
|
25
33
|
# for valid syntax. If :cron is present it will take precedence over :every.
|
26
34
|
#
|
27
|
-
# :class must be a resque worker class
|
35
|
+
# :class must be a resque worker class. If it is missing, the job name (hash key)
|
36
|
+
# will be used as :class.
|
28
37
|
#
|
29
38
|
# :args can be any yaml which will be converted to a ruby literal and
|
30
39
|
# passed in a params. (optional)
|
@@ -36,6 +45,8 @@ module ResqueScheduler
|
|
36
45
|
# params is an array, each element in the array is passed as a separate
|
37
46
|
# param, otherwise params is passed in as the only parameter to perform.
|
38
47
|
def schedule=(schedule_hash)
|
48
|
+
schedule_hash = prepare_schedule(schedule_hash)
|
49
|
+
|
39
50
|
if Resque::Scheduler.dynamic
|
40
51
|
schedule_hash.each do |name, job_spec|
|
41
52
|
set_schedule(name, job_spec)
|
@@ -109,16 +120,11 @@ module ResqueScheduler
|
|
109
120
|
# a queue in which the job will be placed after the
|
110
121
|
# timestamp has passed.
|
111
122
|
def enqueue_at_with_queue(queue, timestamp, klass, *args)
|
112
|
-
|
113
|
-
klass.send(hook,*args)
|
114
|
-
end
|
115
|
-
return false if before_hooks.any? { |result| result == false }
|
123
|
+
return false unless Plugin.run_before_schedule_hooks(klass, *args)
|
116
124
|
|
117
125
|
delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
|
118
126
|
|
119
|
-
|
120
|
-
klass.send(hook,*args)
|
121
|
-
end
|
127
|
+
Plugin.run_after_schedule_hooks(klass, *args)
|
122
128
|
end
|
123
129
|
|
124
130
|
# Identical to enqueue_at but takes number_of_seconds_from_now
|
@@ -264,12 +270,14 @@ module ResqueScheduler
|
|
264
270
|
end
|
265
271
|
end
|
266
272
|
|
267
|
-
def
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
+
def prepare_schedule(schedule_hash)
|
274
|
+
prepared_hash = {}
|
275
|
+
schedule_hash.each do |name, job_spec|
|
276
|
+
job_spec = job_spec.dup
|
277
|
+
job_spec['class'] = name unless job_spec.key?('class') || job_spec.key?(:class)
|
278
|
+
prepared_hash[name] = job_spec
|
279
|
+
end
|
280
|
+
prepared_hash
|
273
281
|
end
|
274
282
|
|
275
283
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ResqueScheduler
|
2
|
+
module Plugin
|
3
|
+
extend self
|
4
|
+
def hooks(job, pattern)
|
5
|
+
job.methods.grep(/^#{pattern}/).sort
|
6
|
+
end
|
7
|
+
|
8
|
+
def run_hooks(job, pattern, *args)
|
9
|
+
results = hooks(job, pattern).collect do |hook|
|
10
|
+
job.send(hook, *args)
|
11
|
+
end
|
12
|
+
|
13
|
+
results.all? { |result| result != false }
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method_name, *args, &block)
|
17
|
+
if method_name =~ /^run_(.*)_hooks$/
|
18
|
+
job = args.shift
|
19
|
+
run_hooks job, $1, *args
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,52 +1,23 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/test_helper'
|
2
2
|
|
3
3
|
context "scheduling jobs with hooks" do
|
4
|
-
class JobThatCannotBeScheduledWithoutArguments < Resque::Job
|
5
|
-
@queue = :job_that_cannot_be_scheduled_without_arguments
|
6
|
-
def self.perform(*x);end
|
7
|
-
def self.before_schedule_return_nil_if_arguments_not_supplied(*args)
|
8
|
-
counters[:before_schedule] += 1
|
9
|
-
return false if args.empty?
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.after_schedule_do_something(*args)
|
13
|
-
counters[:after_schedule] += 1
|
14
|
-
end
|
15
|
-
|
16
|
-
class << self
|
17
|
-
def counters
|
18
|
-
@counters ||= Hash.new{|h,k| h[k]=0}
|
19
|
-
end
|
20
|
-
def clean
|
21
|
-
counters.clear
|
22
|
-
self
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
4
|
setup do
|
28
|
-
Resque
|
29
|
-
Resque.redis.del(:schedules)
|
30
|
-
Resque.redis.del(:schedules_changed)
|
31
|
-
Resque::Scheduler.mute = true
|
32
|
-
Resque::Scheduler.clear_schedule!
|
33
|
-
Resque::Scheduler.send(:class_variable_set, :@@scheduled_jobs, {})
|
5
|
+
Resque.redis.flushall
|
34
6
|
end
|
35
7
|
|
36
|
-
test "before_schedule hook that does not return false should
|
37
|
-
enqueue_time = Time.now
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
assert_equal(1,
|
8
|
+
test "before_schedule hook that does not return false should be enqueued" do
|
9
|
+
enqueue_time = Time.now
|
10
|
+
SomeRealClass.expects(:before_schedule_example).with(:foo)
|
11
|
+
SomeRealClass.expects(:after_schedule_example).with(:foo)
|
12
|
+
Resque.enqueue_at(enqueue_time.to_i, SomeRealClass, :foo)
|
13
|
+
assert_equal(1, Resque.delayed_timestamp_size(enqueue_time.to_i), "job should be enqueued")
|
42
14
|
end
|
43
15
|
|
44
|
-
test "before_schedule hook that returns false should
|
45
|
-
enqueue_time = Time.now
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
assert_equal(
|
50
|
-
assert_equal(0, JobThatCannotBeScheduledWithoutArguments.counters[:after_schedule], 'after_schedule was run')
|
16
|
+
test "before_schedule hook that returns false should not be enqueued" do
|
17
|
+
enqueue_time = Time.now
|
18
|
+
SomeRealClass.expects(:before_schedule_example).with(:foo).returns(false)
|
19
|
+
SomeRealClass.expects(:after_schedule_example).never
|
20
|
+
Resque.enqueue_at(enqueue_time.to_i, SomeRealClass, :foo)
|
21
|
+
assert_equal(0, Resque.delayed_timestamp_size(enqueue_time.to_i), "job should not be enqueued")
|
51
22
|
end
|
52
23
|
end
|
data/test/scheduler_test.rb
CHANGED
@@ -25,7 +25,9 @@ context "Resque::Scheduler" do
|
|
25
25
|
config = {'cron' => "* * * * *", 'class' => 'SomeRealClass', 'args' => "/tmp"}
|
26
26
|
|
27
27
|
Resque::Job.expects(:create).with(SomeRealClass.queue, SomeRealClass, '/tmp')
|
28
|
-
SomeRealClass.expects(:
|
28
|
+
SomeRealClass.expects(:before_delayed_enqueue_example).with("/tmp")
|
29
|
+
SomeRealClass.expects(:before_enqueue_example).with("/tmp")
|
30
|
+
SomeRealClass.expects(:after_enqueue_example).with("/tmp")
|
29
31
|
|
30
32
|
Resque::Scheduler.enqueue_from_config(config)
|
31
33
|
end
|
@@ -69,6 +71,24 @@ context "Resque::Scheduler" do
|
|
69
71
|
assert_equal(1, Resque::Scheduler.scheduled_jobs.size)
|
70
72
|
assert Resque::Scheduler.scheduled_jobs.keys.include?("some_ivar_job")
|
71
73
|
end
|
74
|
+
|
75
|
+
test "load_schedule_job with every with options" do
|
76
|
+
Resque::Scheduler.load_schedule_job("some_ivar_job", {'every' => ['30s', {'first_in' => '60s'}], 'class' => 'SomeIvarJob', 'args' => "/tmp"})
|
77
|
+
|
78
|
+
assert_equal(1, Resque::Scheduler.rufus_scheduler.all_jobs.size)
|
79
|
+
assert_equal(1, Resque::Scheduler.scheduled_jobs.size)
|
80
|
+
assert Resque::Scheduler.scheduled_jobs.keys.include?("some_ivar_job")
|
81
|
+
assert Resque::Scheduler.scheduled_jobs["some_ivar_job"].params.keys.include?(:first_in)
|
82
|
+
end
|
83
|
+
|
84
|
+
test "load_schedule_job with cron with options" do
|
85
|
+
Resque::Scheduler.load_schedule_job("some_ivar_job", {'cron' => ['* * * * *', {'allow_overlapping' => 'true'}], 'class' => 'SomeIvarJob', 'args' => "/tmp"})
|
86
|
+
|
87
|
+
assert_equal(1, Resque::Scheduler.rufus_scheduler.all_jobs.size)
|
88
|
+
assert_equal(1, Resque::Scheduler.scheduled_jobs.size)
|
89
|
+
assert Resque::Scheduler.scheduled_jobs.keys.include?("some_ivar_job")
|
90
|
+
assert Resque::Scheduler.scheduled_jobs["some_ivar_job"].params.keys.include?(:allow_overlapping)
|
91
|
+
end
|
72
92
|
|
73
93
|
test "load_schedule_job without cron" do
|
74
94
|
Resque::Scheduler.load_schedule_job("some_ivar_job", {'class' => 'SomeIvarJob', 'args' => "/tmp"})
|
@@ -165,6 +185,24 @@ context "Resque::Scheduler" do
|
|
165
185
|
Resque.decode(Resque.redis.hget(:schedules, "my_ivar_job")))
|
166
186
|
end
|
167
187
|
|
188
|
+
test "schedule= uses job name as 'class' argument if it's missing" do
|
189
|
+
Resque::Scheduler.dynamic = true
|
190
|
+
Resque.schedule = {"SomeIvarJob" => {
|
191
|
+
'cron' => "* * * * *", 'args' => "/tmp/75"
|
192
|
+
}}
|
193
|
+
assert_equal({'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/75"},
|
194
|
+
Resque.decode(Resque.redis.hget(:schedules, "SomeIvarJob")))
|
195
|
+
assert_equal('SomeIvarJob', Resque.schedule['SomeIvarJob']['class'])
|
196
|
+
end
|
197
|
+
|
198
|
+
test "schedule= does not mutate argument" do
|
199
|
+
schedule = {"SomeIvarJob" => {
|
200
|
+
'cron' => "* * * * *", 'args' => "/tmp/75"
|
201
|
+
}}
|
202
|
+
Resque.schedule = schedule
|
203
|
+
assert !schedule['SomeIvarJob'].key?('class')
|
204
|
+
end
|
205
|
+
|
168
206
|
test "set_schedule can set an individual schedule" do
|
169
207
|
Resque.set_schedule("some_ivar_job", {
|
170
208
|
'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp/22"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.
|
4
|
+
version: 2.0.0.h
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-19 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
16
|
-
requirement: &
|
16
|
+
requirement: &2169473600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.0.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2169473600
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: redis
|
27
|
-
requirement: &
|
27
|
+
requirement: &2169465640 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 2.0.1
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2169465640
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: resque
|
38
|
-
requirement: &
|
38
|
+
requirement: &2169464480 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 1.19.0
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2169464480
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rufus-scheduler
|
49
|
-
requirement: &
|
49
|
+
requirement: &2169462660 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2169462660
|
58
58
|
description: ! "Light weight job scheduling on top of Resque.\n Adds methods enqueue_at/enqueue_in
|
59
59
|
to schedule jobs in the future.\n Also supports queueing jobs on a fixed, cron-like
|
60
60
|
schedule."
|
@@ -72,6 +72,7 @@ files:
|
|
72
72
|
- Rakefile
|
73
73
|
- lib/resque/scheduler.rb
|
74
74
|
- lib/resque_scheduler.rb
|
75
|
+
- lib/resque_scheduler/plugin.rb
|
75
76
|
- lib/resque_scheduler/server.rb
|
76
77
|
- lib/resque_scheduler/server/views/delayed.erb
|
77
78
|
- lib/resque_scheduler/server/views/delayed_timestamp.erb
|
@@ -101,7 +102,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
102
|
version: '0'
|
102
103
|
segments:
|
103
104
|
- 0
|
104
|
-
hash: -
|
105
|
+
hash: -2739279222393439195
|
105
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
107
|
none: false
|
107
108
|
requirements:
|
@@ -110,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
110
111
|
version: 1.3.6
|
111
112
|
requirements: []
|
112
113
|
rubyforge_project:
|
113
|
-
rubygems_version: 1.8.
|
114
|
+
rubygems_version: 1.8.17
|
114
115
|
signing_key:
|
115
116
|
specification_version: 3
|
116
117
|
summary: Light weight job scheduling on top of Resque
|