resque-scheduler 2.0.0 → 2.3.1
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.
Potentially problematic release.
This version of resque-scheduler might be problematic. Click here for more details.
- checksums.yaml +15 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +129 -0
- data/.travis.yml +18 -0
- data/AUTHORS.md +63 -0
- data/CONTRIBUTING.md +6 -0
- data/Gemfile +3 -7
- data/HISTORY.md +65 -6
- data/LICENSE +3 -1
- data/{README.markdown → README.md} +218 -113
- data/Rakefile +22 -9
- data/lib/resque/scheduler/lock/base.rb +52 -0
- data/lib/resque/scheduler/lock/basic.rb +28 -0
- data/lib/resque/scheduler/lock/resilient.rb +69 -0
- data/lib/resque/scheduler/lock.rb +3 -0
- data/lib/resque/scheduler.rb +56 -19
- data/lib/resque/scheduler_locking.rb +89 -0
- data/lib/resque_scheduler/logger_builder.rb +51 -0
- data/lib/resque_scheduler/server/views/delayed.erb +2 -1
- data/lib/resque_scheduler/server/views/requeue-params.erb +23 -0
- data/lib/resque_scheduler/server/views/scheduler.erb +4 -3
- data/lib/resque_scheduler/server.rb +25 -3
- data/lib/resque_scheduler/tasks.rb +9 -3
- data/lib/resque_scheduler/util.rb +34 -0
- data/lib/resque_scheduler/version.rb +3 -1
- data/lib/resque_scheduler.rb +89 -38
- data/resque-scheduler.gemspec +26 -20
- data/script/migrate_to_timestamps_set.rb +14 -0
- data/test/delayed_queue_test.rb +67 -16
- data/test/redis-test.conf +0 -7
- data/test/resque-web_test.rb +105 -3
- data/test/scheduler_args_test.rb +1 -1
- data/test/scheduler_locking_test.rb +180 -0
- data/test/scheduler_setup_test.rb +59 -0
- data/test/scheduler_test.rb +21 -12
- data/test/support/redis_instance.rb +129 -0
- data/test/test_helper.rb +26 -14
- metadata +105 -32
data/lib/resque_scheduler.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'resque'
|
3
3
|
require 'resque_scheduler/version'
|
4
|
+
require 'resque_scheduler/util'
|
4
5
|
require 'resque/scheduler'
|
5
6
|
require 'resque_scheduler/plugin'
|
6
7
|
|
7
8
|
module ResqueScheduler
|
8
|
-
|
9
9
|
#
|
10
10
|
# Accepts a new schedule configuration of the form:
|
11
11
|
#
|
@@ -25,6 +25,9 @@ module ResqueScheduler
|
|
25
25
|
# is used implicitly as "class" argument - in the "MakeTea" example,
|
26
26
|
# "MakeTea" is used both as job name and resque worker class.
|
27
27
|
#
|
28
|
+
# Any jobs that were in the old schedule, but are not
|
29
|
+
# present in the new schedule, will be removed.
|
30
|
+
#
|
28
31
|
# :cron can be any cron scheduling string
|
29
32
|
#
|
30
33
|
# :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage
|
@@ -43,19 +46,27 @@ module ResqueScheduler
|
|
43
46
|
# params is an array, each element in the array is passed as a separate
|
44
47
|
# param, otherwise params is passed in as the only parameter to perform.
|
45
48
|
def schedule=(schedule_hash)
|
49
|
+
# clean the schedules as it exists in redis
|
50
|
+
clean_schedules
|
51
|
+
|
46
52
|
schedule_hash = prepare_schedule(schedule_hash)
|
47
53
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
54
|
+
# store all schedules in redis, so we can retrieve them back everywhere.
|
55
|
+
schedule_hash.each do |name, job_spec|
|
56
|
+
set_schedule(name, job_spec)
|
52
57
|
end
|
53
|
-
|
58
|
+
|
59
|
+
# ensure only return the successfully saved data!
|
60
|
+
reload_schedule!
|
54
61
|
end
|
55
62
|
|
56
63
|
# Returns the schedule hash
|
57
64
|
def schedule
|
58
|
-
@schedule ||=
|
65
|
+
@schedule ||= get_schedules
|
66
|
+
if @schedule.nil?
|
67
|
+
return {}
|
68
|
+
end
|
69
|
+
@schedule
|
59
70
|
end
|
60
71
|
|
61
72
|
# reloads the schedule from redis
|
@@ -63,17 +74,28 @@ module ResqueScheduler
|
|
63
74
|
@schedule = get_schedules
|
64
75
|
end
|
65
76
|
|
66
|
-
# gets the
|
77
|
+
# gets the schedules as it exists in redis
|
67
78
|
def get_schedules
|
79
|
+
unless redis.exists(:schedules)
|
80
|
+
return nil
|
81
|
+
end
|
82
|
+
|
83
|
+
redis.hgetall(:schedules).tap do |h|
|
84
|
+
h.each do |name, config|
|
85
|
+
h[name] = decode(config)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# clean the schedules as it exists in redis, useful for first setup?
|
91
|
+
def clean_schedules
|
68
92
|
if redis.exists(:schedules)
|
69
|
-
redis.
|
70
|
-
|
71
|
-
h[name] = decode(config)
|
72
|
-
end
|
93
|
+
redis.hkeys(:schedules).each do |key|
|
94
|
+
remove_schedule(key)
|
73
95
|
end
|
74
|
-
else
|
75
|
-
nil
|
76
96
|
end
|
97
|
+
@schedule = nil
|
98
|
+
true
|
77
99
|
end
|
78
100
|
|
79
101
|
# Create or update a schedule with the provided name and configuration.
|
@@ -110,7 +132,7 @@ module ResqueScheduler
|
|
110
132
|
# for queueing. Until timestamp is in the past, the job will
|
111
133
|
# sit in the schedule list.
|
112
134
|
def enqueue_at(timestamp, klass, *args)
|
113
|
-
|
135
|
+
validate(klass)
|
114
136
|
enqueue_at_with_queue(queue_from_class(klass), timestamp, klass, *args)
|
115
137
|
end
|
116
138
|
|
@@ -121,9 +143,15 @@ module ResqueScheduler
|
|
121
143
|
def enqueue_at_with_queue(queue, timestamp, klass, *args)
|
122
144
|
return false unless Plugin.run_before_schedule_hooks(klass, *args)
|
123
145
|
|
124
|
-
if Resque.inline?
|
146
|
+
if Resque.inline? || timestamp.to_i < Time.now.to_i
|
125
147
|
# Just create the job and let resque perform it right away with inline.
|
126
|
-
|
148
|
+
# If the class is a custom job class, call self#scheduled on it. This allows you to do things like
|
149
|
+
# Resque.enqueue_at(timestamp, CustomJobClass, :opt1 => val1). Otherwise, pass off to Resque.
|
150
|
+
if klass.respond_to?(:scheduled)
|
151
|
+
klass.scheduled(queue, klass.to_s(), *args)
|
152
|
+
else
|
153
|
+
Resque::Job.create(queue, klass, *args)
|
154
|
+
end
|
127
155
|
else
|
128
156
|
delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
|
129
157
|
end
|
@@ -152,6 +180,9 @@ module ResqueScheduler
|
|
152
180
|
# First add this item to the list for this timestamp
|
153
181
|
redis.rpush("delayed:#{timestamp.to_i}", encode(item))
|
154
182
|
|
183
|
+
# Store the timestamps at with this item occurs
|
184
|
+
redis.sadd("timestamps:#{encode(item)}", "delayed:#{timestamp.to_i}")
|
185
|
+
|
155
186
|
# Now, add this timestamp to the zsets. The score and the value are
|
156
187
|
# the same since we'll be querying by timestamp, and we don't have
|
157
188
|
# anything else to store.
|
@@ -160,7 +191,7 @@ module ResqueScheduler
|
|
160
191
|
|
161
192
|
# Returns an array of timestamps based on start and count
|
162
193
|
def delayed_queue_peek(start, count)
|
163
|
-
Array(redis.zrange(:delayed_queue_schedule, start, start+count-1)).collect{|x| x.to_i}
|
194
|
+
Array(redis.zrange(:delayed_queue_schedule, start, start+count-1)).collect { |x| x.to_i }
|
164
195
|
end
|
165
196
|
|
166
197
|
# Returns the size of the delayed queue schedule
|
@@ -197,7 +228,9 @@ module ResqueScheduler
|
|
197
228
|
def next_item_for_timestamp(timestamp)
|
198
229
|
key = "delayed:#{timestamp.to_i}"
|
199
230
|
|
200
|
-
|
231
|
+
encoded_item = redis.lpop(key)
|
232
|
+
redis.srem("timestamps:#{encoded_item}", key)
|
233
|
+
item = decode(encoded_item)
|
201
234
|
|
202
235
|
# If the list is empty, remove it.
|
203
236
|
clean_up_timestamp(key, timestamp)
|
@@ -207,23 +240,36 @@ module ResqueScheduler
|
|
207
240
|
# Clears all jobs created with enqueue_at or enqueue_in
|
208
241
|
def reset_delayed_queue
|
209
242
|
Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |item|
|
210
|
-
|
243
|
+
key = "delayed:#{item}"
|
244
|
+
items = redis.lrange(key, 0, -1)
|
245
|
+
redis.pipelined do
|
246
|
+
items.each { |ts_item| redis.del("timestamps:#{ts_item}") }
|
247
|
+
end
|
248
|
+
redis.del key
|
211
249
|
end
|
212
250
|
|
213
251
|
redis.del :delayed_queue_schedule
|
214
252
|
end
|
215
253
|
|
216
254
|
# Given an encoded item, remove it from the delayed_queue
|
217
|
-
#
|
218
|
-
# This method is potentially very expensive since it needs to scan
|
219
|
-
# through the delayed queue for every timestamp.
|
220
255
|
def remove_delayed(klass, *args)
|
221
|
-
destroyed = 0
|
222
256
|
search = encode(job_to_hash(klass, args))
|
223
|
-
|
224
|
-
|
257
|
+
timestamps = redis.smembers("timestamps:#{search}")
|
258
|
+
|
259
|
+
replies = redis.pipelined do
|
260
|
+
timestamps.each do |key|
|
261
|
+
redis.lrem(key, 0, search)
|
262
|
+
redis.srem("timestamps:#{search}", key)
|
263
|
+
end
|
225
264
|
end
|
226
|
-
|
265
|
+
|
266
|
+
(replies.nil? || replies.empty?) ? 0 : replies.each_slice(2).collect { |slice| slice.first }.inject(:+)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Given an encoded item, enqueue it now
|
270
|
+
def enqueue_delayed(klass, *args)
|
271
|
+
hash = job_to_hash(klass, args)
|
272
|
+
remove_delayed(klass, *args).times { Resque::Scheduler.enqueue_from_config(hash) }
|
227
273
|
end
|
228
274
|
|
229
275
|
# Given a timestamp and job (klass + args) it removes all instances and
|
@@ -233,8 +279,12 @@ module ResqueScheduler
|
|
233
279
|
# timestamp
|
234
280
|
def remove_delayed_job_from_timestamp(timestamp, klass, *args)
|
235
281
|
key = "delayed:#{timestamp.to_i}"
|
236
|
-
|
282
|
+
encoded_job = encode(job_to_hash(klass, args))
|
283
|
+
|
284
|
+
redis.srem("timestamps:#{encoded_job}", key)
|
285
|
+
count = redis.lrem(key, 0, encoded_job)
|
237
286
|
clean_up_timestamp(key, timestamp)
|
287
|
+
|
238
288
|
count
|
239
289
|
end
|
240
290
|
|
@@ -246,6 +296,14 @@ module ResqueScheduler
|
|
246
296
|
total_jobs
|
247
297
|
end
|
248
298
|
|
299
|
+
# Returns delayed jobs schedule timestamp for +klass+, +args+.
|
300
|
+
def scheduled_at(klass, *args)
|
301
|
+
search = encode(job_to_hash(klass, args))
|
302
|
+
redis.smembers("timestamps:#{search}").collect do |key|
|
303
|
+
key.tr('delayed:', '').to_i
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
249
307
|
private
|
250
308
|
|
251
309
|
def job_to_hash(klass, args)
|
@@ -258,6 +316,9 @@ module ResqueScheduler
|
|
258
316
|
|
259
317
|
def clean_up_timestamp(key, timestamp)
|
260
318
|
# If the list is empty, remove it.
|
319
|
+
|
320
|
+
# Use a watch here to ensure nobody adds jobs to this delayed
|
321
|
+
# queue while we're removing it.
|
261
322
|
redis.watch key
|
262
323
|
if 0 == redis.llen(key).to_i
|
263
324
|
redis.multi do
|
@@ -268,15 +329,6 @@ module ResqueScheduler
|
|
268
329
|
redis.unwatch
|
269
330
|
end
|
270
331
|
end
|
271
|
-
def validate_job!(klass)
|
272
|
-
if klass.to_s.empty?
|
273
|
-
raise Resque::NoClassError.new("Jobs must be given a class.")
|
274
|
-
end
|
275
|
-
|
276
|
-
unless queue_from_class(klass)
|
277
|
-
raise Resque::NoQueueError.new("Jobs must be placed onto a queue.")
|
278
|
-
end
|
279
|
-
end
|
280
332
|
|
281
333
|
def prepare_schedule(schedule_hash)
|
282
334
|
prepared_hash = {}
|
@@ -287,7 +339,6 @@ module ResqueScheduler
|
|
287
339
|
end
|
288
340
|
prepared_hash
|
289
341
|
end
|
290
|
-
|
291
342
|
end
|
292
343
|
|
293
344
|
Resque.extend ResqueScheduler
|
data/resque-scheduler.gemspec
CHANGED
@@ -1,27 +1,33 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'resque_scheduler/version'
|
4
5
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
s.description = %q{Light weight job scheduling on top of Resque.
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'resque-scheduler'
|
8
|
+
spec.version = ResqueScheduler::VERSION
|
9
|
+
spec.authors = ['Ben VandenBos']
|
10
|
+
spec.email = ['bvandenbos@gmail.com']
|
11
|
+
spec.homepage = 'http://github.com/resque/resque-scheduler'
|
12
|
+
spec.summary = 'Light weight job scheduling on top of Resque'
|
13
|
+
spec.description = %q{Light weight job scheduling on top of Resque.
|
14
14
|
Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
|
15
15
|
Also supports queueing jobs on a fixed, cron-like schedule.}
|
16
|
+
spec.license = 'MIT'
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
spec.files = `git ls-files`.split("\n")
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^test/})
|
21
|
+
spec.require_paths = ['lib']
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
|
+
spec.add_development_dependency 'mocha'
|
25
|
+
spec.add_development_dependency 'rack-test'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'json' if RUBY_VERSION < '1.9'
|
28
|
+
spec.add_development_dependency 'rubocop' unless RUBY_VERSION < '1.9'
|
23
29
|
|
24
|
-
|
25
|
-
|
26
|
-
|
30
|
+
spec.add_runtime_dependency 'redis', '>= 3.0.0'
|
31
|
+
spec.add_runtime_dependency 'resque', '~> 1.25'
|
32
|
+
spec.add_runtime_dependency 'rufus-scheduler', '~> 2.0'
|
27
33
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'resque'
|
3
|
+
|
4
|
+
if ARGV.size != 1
|
5
|
+
puts "migrate_to_timestamps_set.rb <redis-host:redis-port>"
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
Resque.redis = ARGV[0]
|
10
|
+
redis = Resque.redis
|
11
|
+
Array(redis.keys("delayed:*")).each do |key|
|
12
|
+
jobs = redis.lrange(key, 0, -1)
|
13
|
+
jobs.each {|job| redis.sadd("timestamps:#{job}", key)}
|
14
|
+
end
|
data/test/delayed_queue_test.rb
CHANGED
@@ -8,20 +8,21 @@ context "DelayedQueue" do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
test "enqueue_at adds correct list and zset" do
|
11
|
-
timestamp = Time.now
|
11
|
+
timestamp = Time.now + 1
|
12
|
+
encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => ["path"], :queue => Resque.queue_from_class(SomeIvarJob))
|
12
13
|
|
13
14
|
assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
|
15
|
+
assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty to start")
|
14
16
|
|
15
17
|
Resque.enqueue_at(timestamp, SomeIvarJob, "path")
|
16
18
|
|
17
19
|
# Confirm the correct keys were added
|
18
20
|
assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
|
21
|
+
assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps should have one entry now")
|
19
22
|
assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
|
20
23
|
|
21
|
-
read_timestamp =
|
24
|
+
read_timestamp = timestamp.to_i
|
22
25
|
|
23
|
-
# Confirm the timestamp came out correctly
|
24
|
-
assert_equal(timestamp.to_i, read_timestamp, "The timestamp we pull out of redis should match the one we put in")
|
25
26
|
item = Resque.next_item_for_timestamp(read_timestamp)
|
26
27
|
|
27
28
|
# Confirm the item came out correctly
|
@@ -31,23 +32,25 @@ context "DelayedQueue" do
|
|
31
32
|
# And now confirm the keys are gone
|
32
33
|
assert(!Resque.redis.exists("delayed:#{timestamp.to_i}"))
|
33
34
|
assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), "delayed queue should be empty")
|
35
|
+
assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty")
|
34
36
|
end
|
35
37
|
|
36
38
|
test "enqueue_at with queue adds correct list and zset and queue" do
|
37
|
-
timestamp = Time.now
|
39
|
+
timestamp = Time.now + 1
|
40
|
+
encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => ["path"], :queue => 'critical')
|
38
41
|
|
39
42
|
assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
|
43
|
+
assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty to start")
|
40
44
|
|
41
45
|
Resque.enqueue_at_with_queue('critical', timestamp, SomeIvarJob, "path")
|
42
46
|
|
43
47
|
# Confirm the correct keys were added
|
44
48
|
assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
|
49
|
+
assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps should have one entry now")
|
45
50
|
assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
|
46
51
|
|
47
|
-
read_timestamp =
|
52
|
+
read_timestamp = timestamp.to_i
|
48
53
|
|
49
|
-
# Confirm the timestamp came out correctly
|
50
|
-
assert_equal(timestamp.to_i, read_timestamp, "The timestamp we pull out of redis should match the one we put in")
|
51
54
|
item = Resque.next_item_for_timestamp(read_timestamp)
|
52
55
|
|
53
56
|
# Confirm the item came out correctly
|
@@ -58,17 +61,21 @@ context "DelayedQueue" do
|
|
58
61
|
# And now confirm the keys are gone
|
59
62
|
assert(!Resque.redis.exists("delayed:#{timestamp.to_i}"))
|
60
63
|
assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), "delayed queue should be empty")
|
64
|
+
assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty")
|
61
65
|
end
|
62
66
|
|
63
67
|
test "a job in the future doesn't come out" do
|
64
68
|
timestamp = Time.now + 600 # 10 minutes from now (in the future, shouldn't come out)
|
69
|
+
encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => ["path"], :queue => Resque.queue_from_class(SomeIvarJob))
|
65
70
|
|
66
71
|
assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
|
72
|
+
assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty to start")
|
67
73
|
|
68
74
|
Resque.enqueue_at(timestamp, SomeIvarJob, "path")
|
69
75
|
|
70
76
|
# Confirm the correct keys were added
|
71
77
|
assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
|
78
|
+
assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps should have one entry now")
|
72
79
|
assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
|
73
80
|
|
74
81
|
read_timestamp = Resque.next_delayed_timestamp
|
@@ -88,19 +95,21 @@ context "DelayedQueue" do
|
|
88
95
|
|
89
96
|
test "enqueue_at and enqueue_in are equivelent" do
|
90
97
|
timestamp = Time.now + 60
|
98
|
+
encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => ["path"], :queue => Resque.queue_from_class(SomeIvarJob))
|
91
99
|
|
92
100
|
Resque.enqueue_at(timestamp, SomeIvarJob, "path")
|
93
101
|
Resque.enqueue_in(timestamp - Time.now, SomeIvarJob, "path")
|
94
102
|
|
95
103
|
assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "should have one timestamp in the delayed queue")
|
96
104
|
assert_equal(2, Resque.redis.llen("delayed:#{timestamp.to_i}"), "should have 2 items in the timestamp queue")
|
105
|
+
assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps should have one entry now")
|
97
106
|
end
|
98
107
|
|
99
108
|
test "empty delayed_queue_peek returns empty array" do
|
100
109
|
assert_equal([], Resque.delayed_queue_peek(0,20))
|
101
110
|
end
|
102
111
|
|
103
|
-
test "
|
112
|
+
test "delayed_queue_peek returns stuff" do
|
104
113
|
t = Time.now
|
105
114
|
expected_timestamps = (1..5).to_a.map do |i|
|
106
115
|
(t + 60 + i).to_i
|
@@ -166,12 +175,11 @@ context "DelayedQueue" do
|
|
166
175
|
|
167
176
|
test "handle_delayed_item with items" do
|
168
177
|
t = Time.now - 60 # in the past
|
169
|
-
Resque.enqueue_at(t, SomeIvarJob)
|
170
|
-
Resque.enqueue_at(t, SomeIvarJob)
|
171
178
|
|
172
179
|
# 2 SomeIvarJob jobs should be created in the "ivar" queue
|
173
|
-
Resque::Job.expects(:create).twice.with(
|
174
|
-
Resque
|
180
|
+
Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob)
|
181
|
+
Resque.enqueue_at(t, SomeIvarJob)
|
182
|
+
Resque.enqueue_at(t, SomeIvarJob)
|
175
183
|
end
|
176
184
|
|
177
185
|
test "handle_delayed_items with items in the future" do
|
@@ -184,6 +192,24 @@ context "DelayedQueue" do
|
|
184
192
|
Resque::Scheduler.handle_delayed_items(t)
|
185
193
|
end
|
186
194
|
|
195
|
+
test "calls klass#scheduled when enqueuing jobs if it exists" do
|
196
|
+
t = Time.now - 60
|
197
|
+
FakeCustomJobClassEnqueueAt.expects(:scheduled).once.with(:test, FakeCustomJobClassEnqueueAt.to_s, {:foo => "bar"})
|
198
|
+
Resque.enqueue_at(t, FakeCustomJobClassEnqueueAt, :foo => "bar")
|
199
|
+
end
|
200
|
+
|
201
|
+
test "when Resque.inline = true, calls klass#scheduled when enqueuing jobs if it exists" do
|
202
|
+
old_val = Resque.inline
|
203
|
+
begin
|
204
|
+
Resque.inline = true
|
205
|
+
t = Time.now - 60
|
206
|
+
FakeCustomJobClassEnqueueAt.expects(:scheduled).once.with(:test, FakeCustomJobClassEnqueueAt.to_s, {:foo => "bar"})
|
207
|
+
Resque.enqueue_at(t, FakeCustomJobClassEnqueueAt, :foo => "bar")
|
208
|
+
ensure
|
209
|
+
Resque.inline = old_val
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
187
213
|
test "enqueue_delayed_items_for_timestamp creates jobs and empties the delayed queue" do
|
188
214
|
t = Time.now + 60
|
189
215
|
|
@@ -199,6 +225,24 @@ context "DelayedQueue" do
|
|
199
225
|
assert_equal(0, Resque.delayed_timestamp_peek(t, 0, 3).length)
|
200
226
|
end
|
201
227
|
|
228
|
+
test "enqueue_delayed creates jobs and empties the delayed queue" do
|
229
|
+
t = Time.now + 60
|
230
|
+
|
231
|
+
Resque.enqueue_at(t, SomeIvarJob, "foo")
|
232
|
+
Resque.enqueue_at(t, SomeIvarJob, "bar")
|
233
|
+
Resque.enqueue_at(t, SomeIvarJob, "bar")
|
234
|
+
|
235
|
+
# 3 SomeIvarJob jobs should be created in the "ivar" queue
|
236
|
+
Resque::Job.expects(:create).never.with(:ivar, SomeIvarJob, "foo")
|
237
|
+
Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob, "bar")
|
238
|
+
|
239
|
+
# 2 SomeIvarJob jobs should be enqueued
|
240
|
+
assert_equal(2, Resque.enqueue_delayed(SomeIvarJob, "bar"))
|
241
|
+
|
242
|
+
# delayed queue for timestamp should have one remaining
|
243
|
+
assert_equal(1, Resque.delayed_timestamp_peek(t, 0, 3).length)
|
244
|
+
end
|
245
|
+
|
202
246
|
test "handle_delayed_items works with out specifying queue (upgrade case)" do
|
203
247
|
t = Time.now - 60
|
204
248
|
Resque.delayed_push(t, :class => 'SomeIvarJob')
|
@@ -218,13 +262,23 @@ context "DelayedQueue" do
|
|
218
262
|
|
219
263
|
Resque.reset_delayed_queue
|
220
264
|
assert_equal(0, Resque.delayed_queue_schedule_size)
|
265
|
+
assert_equal(0, Resque.redis.keys("timestamps:*").size)
|
221
266
|
end
|
222
267
|
|
223
268
|
test "remove_delayed removes job and returns the count" do
|
224
269
|
t = Time.now + 120
|
270
|
+
encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => [], :queue => Resque.queue_from_class(SomeIvarJob))
|
225
271
|
Resque.enqueue_at(t, SomeIvarJob)
|
226
272
|
|
227
273
|
assert_equal(1, Resque.remove_delayed(SomeIvarJob))
|
274
|
+
assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"))
|
275
|
+
end
|
276
|
+
|
277
|
+
test "scheduled_at returns an array containing job schedule time" do
|
278
|
+
t = Time.now + 120
|
279
|
+
Resque.enqueue_at(t, SomeIvarJob)
|
280
|
+
|
281
|
+
assert_equal([t.to_i], Resque.scheduled_at(SomeIvarJob))
|
228
282
|
end
|
229
283
|
|
230
284
|
test "remove_delayed doesn't remove things it shouldn't" do
|
@@ -304,9 +358,6 @@ context "DelayedQueue" do
|
|
304
358
|
end
|
305
359
|
|
306
360
|
test "invalid job class" do
|
307
|
-
assert_raise Resque::NoClassError do
|
308
|
-
Resque.enqueue_in(10, nil)
|
309
|
-
end
|
310
361
|
assert_raise Resque::NoQueueError do
|
311
362
|
Resque.enqueue_in(10, String) # string serves as invalid Job class
|
312
363
|
end
|
data/test/redis-test.conf
CHANGED
@@ -106,10 +106,3 @@ databases 16
|
|
106
106
|
# errors for write operations, and this may even lead to DB inconsistency.
|
107
107
|
|
108
108
|
# maxmemory <bytes>
|
109
|
-
|
110
|
-
############################### ADVANCED CONFIG ###############################
|
111
|
-
|
112
|
-
# Glue small output buffers together in order to send small replies in a
|
113
|
-
# single TCP packet. Uses a bit more CPU but most of the times it is a win
|
114
|
-
# in terms of number of queries per second. Use 'yes' if unsure.
|
115
|
-
glueoutputbuf yes
|
data/test/resque-web_test.rb
CHANGED
@@ -10,10 +10,24 @@ context "on GET to /schedule" do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
context "on GET to /schedule with scheduled jobs" do
|
13
|
-
setup do
|
13
|
+
setup do
|
14
14
|
ENV['rails_env'] = 'production'
|
15
|
-
Resque.schedule = {
|
16
|
-
|
15
|
+
Resque.schedule = {
|
16
|
+
'some_ivar_job' => {
|
17
|
+
'cron' => "* * * * *",
|
18
|
+
'class' => 'SomeIvarJob',
|
19
|
+
'args' => "/tmp",
|
20
|
+
'rails_env' => 'production'
|
21
|
+
},
|
22
|
+
'some_other_job' => {
|
23
|
+
'every' => ['5m'],
|
24
|
+
'queue' => 'high',
|
25
|
+
'class' => 'SomeOtherJob',
|
26
|
+
'args' => {
|
27
|
+
'b' => 'blah'
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
17
31
|
Resque::Scheduler.load_schedule!
|
18
32
|
get "/schedule"
|
19
33
|
end
|
@@ -30,3 +44,91 @@ context "on GET to /delayed" do
|
|
30
44
|
|
31
45
|
should_respond_with_success
|
32
46
|
end
|
47
|
+
|
48
|
+
def resque_schedule
|
49
|
+
{
|
50
|
+
'job_without_params' => {
|
51
|
+
'cron' => '* * * * *',
|
52
|
+
'class' => 'JobWithoutParams',
|
53
|
+
'args' => {
|
54
|
+
'host' => 'localhost'
|
55
|
+
},
|
56
|
+
'rails_env' => 'production'},
|
57
|
+
'job_with_params' => {
|
58
|
+
'cron' => '* * * * *',
|
59
|
+
'class' => 'JobWithParams',
|
60
|
+
'args' => {
|
61
|
+
'host' => 'localhost'
|
62
|
+
},
|
63
|
+
'parameters' => {
|
64
|
+
'log_level' => {
|
65
|
+
'description' => 'The level of logging',
|
66
|
+
'default' => 'warn'
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
context "POST /schedule/requeue" do
|
74
|
+
setup do
|
75
|
+
Resque.schedule = resque_schedule
|
76
|
+
Resque::Scheduler.load_schedule!
|
77
|
+
end
|
78
|
+
|
79
|
+
test 'job without params' do
|
80
|
+
# Regular jobs without params should redirect to /overview
|
81
|
+
job_name = 'job_without_params'
|
82
|
+
Resque::Scheduler.stubs(:enqueue_from_config).once.with(Resque.schedule[job_name])
|
83
|
+
|
84
|
+
post '/schedule/requeue', {'job_name' => job_name}
|
85
|
+
follow_redirect!
|
86
|
+
assert_equal "http://example.org/overview", last_request.url
|
87
|
+
assert last_response.ok?
|
88
|
+
end
|
89
|
+
|
90
|
+
test 'job with params' do
|
91
|
+
# If a job has params defined,
|
92
|
+
# it should render the template with a form for the job params
|
93
|
+
job_name = 'job_with_params'
|
94
|
+
post '/schedule/requeue', {'job_name' => job_name}
|
95
|
+
|
96
|
+
assert last_response.ok?, last_response.errors
|
97
|
+
assert last_response.body.include?("This job requires parameters")
|
98
|
+
assert last_response.body.include?("<input type=\"hidden\" name=\"job_name\" value=\"#{job_name}\">")
|
99
|
+
|
100
|
+
Resque.schedule[job_name]['parameters'].each do |param_name, param_config|
|
101
|
+
assert last_response.body.include?(
|
102
|
+
"<span style=\"border-bottom:1px dotted;\" title=\"#{param_config['description']}\">(?)</span>")
|
103
|
+
assert last_response.body.include?(
|
104
|
+
"<input type=\"text\" name=\"log_level\" value=\"#{param_config['default']}\">")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "POST /schedule/requeue_with_params" do
|
110
|
+
setup do
|
111
|
+
Resque.schedule = resque_schedule
|
112
|
+
Resque::Scheduler.load_schedule!
|
113
|
+
end
|
114
|
+
|
115
|
+
test 'job with params' do
|
116
|
+
job_name = 'job_with_params'
|
117
|
+
log_level = 'error'
|
118
|
+
|
119
|
+
job_config = Resque.schedule[job_name]
|
120
|
+
args = job_config['args'].merge('log_level' => log_level)
|
121
|
+
job_config = job_config.merge('args' => args)
|
122
|
+
|
123
|
+
Resque::Scheduler.stubs(:enqueue_from_config).once.with(job_config)
|
124
|
+
|
125
|
+
post '/schedule/requeue_with_params', {
|
126
|
+
'job_name' => job_name,
|
127
|
+
'log_level' => log_level
|
128
|
+
}
|
129
|
+
follow_redirect!
|
130
|
+
assert_equal "http://example.org/overview", last_request.url
|
131
|
+
|
132
|
+
assert last_response.ok?, last_response.errors
|
133
|
+
end
|
134
|
+
end
|
data/test/scheduler_args_test.rb
CHANGED
@@ -77,7 +77,7 @@ context "scheduling jobs with arguments" do
|
|
77
77
|
test "calls the worker without arguments when 'args' is blank in the config" do
|
78
78
|
Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
|
79
79
|
class: SomeIvarJob
|
80
|
-
args:
|
80
|
+
args:
|
81
81
|
YAML
|
82
82
|
SomeIvarJob.expects(:perform).once.with()
|
83
83
|
Resque.reserve('ivar').perform
|