resque-scheduler 2.5.5 → 3.0.0
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 +4 -4
- data/.gitignore +12 -6
- data/.rubocop.yml +18 -113
- data/.rubocop_todo.yml +29 -0
- data/.simplecov +3 -1
- data/.travis.yml +12 -4
- data/.vagrant-provision-as-vagrant.sh +15 -0
- data/.vagrant-provision.sh +23 -0
- data/.vagrant-skel/bash_profile +7 -0
- data/.vagrant-skel/bashrc +7 -0
- data/AUTHORS.md +5 -0
- data/Gemfile +1 -2
- data/HISTORY.md +18 -0
- data/README.md +39 -11
- data/ROADMAP.md +0 -6
- data/Rakefile +11 -19
- data/Vagrantfile +14 -0
- data/bin/resque-scheduler +2 -2
- data/examples/Rakefile +1 -1
- data/examples/config/initializers/resque-web.rb +2 -2
- data/examples/dynamic-scheduling/app/jobs/fix_schedules_job.rb +2 -4
- data/examples/dynamic-scheduling/app/jobs/send_email_job.rb +1 -1
- data/examples/dynamic-scheduling/app/models/user.rb +2 -2
- data/examples/dynamic-scheduling/lib/tasks/resque.rake +2 -2
- data/lib/resque-scheduler.rb +3 -1
- data/lib/resque/scheduler.rb +112 -168
- data/lib/resque/scheduler/cli.rb +144 -0
- data/lib/resque/scheduler/configuration.rb +73 -0
- data/lib/resque/scheduler/delaying_extensions.rb +278 -0
- data/lib/resque/scheduler/env.rb +61 -0
- data/lib/resque/scheduler/extension.rb +13 -0
- data/lib/resque/scheduler/lock.rb +2 -1
- data/lib/resque/scheduler/lock/base.rb +6 -2
- data/lib/resque/scheduler/lock/basic.rb +4 -5
- data/lib/resque/scheduler/lock/resilient.rb +30 -37
- data/lib/resque/scheduler/locking.rb +94 -0
- data/lib/resque/scheduler/logger_builder.rb +72 -0
- data/lib/resque/scheduler/plugin.rb +31 -0
- data/lib/resque/scheduler/scheduling_extensions.rb +150 -0
- data/lib/resque/scheduler/server.rb +246 -0
- data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed.erb +2 -1
- data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_schedules.erb +0 -0
- data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_timestamp.erb +0 -0
- data/lib/{resque_scheduler → resque/scheduler}/server/views/requeue-params.erb +0 -0
- data/lib/{resque_scheduler → resque/scheduler}/server/views/scheduler.erb +16 -1
- data/lib/{resque_scheduler → resque/scheduler}/server/views/search.erb +2 -1
- data/lib/{resque_scheduler → resque/scheduler}/server/views/search_form.erb +0 -0
- data/lib/resque/scheduler/signal_handling.rb +40 -0
- data/lib/{resque_scheduler → resque/scheduler}/tasks.rb +3 -5
- data/lib/resque/scheduler/util.rb +41 -0
- data/lib/resque/scheduler/version.rb +7 -0
- data/resque-scheduler.gemspec +21 -19
- data/script/migrate_to_timestamps_set.rb +5 -3
- data/tasks/resque_scheduler.rake +1 -1
- data/test/cli_test.rb +26 -69
- data/test/delayed_queue_test.rb +262 -169
- data/test/env_test.rb +41 -0
- data/test/resque-web_test.rb +169 -48
- data/test/scheduler_args_test.rb +73 -41
- data/test/scheduler_hooks_test.rb +9 -8
- data/test/scheduler_locking_test.rb +55 -36
- data/test/scheduler_setup_test.rb +52 -15
- data/test/scheduler_task_test.rb +15 -10
- data/test/scheduler_test.rb +215 -114
- data/test/support/redis_instance.rb +32 -33
- data/test/test_helper.rb +33 -36
- data/test/util_test.rb +11 -0
- metadata +113 -57
- data/lib/resque/scheduler_locking.rb +0 -91
- data/lib/resque_scheduler.rb +0 -386
- data/lib/resque_scheduler/cli.rb +0 -160
- data/lib/resque_scheduler/logger_builder.rb +0 -72
- data/lib/resque_scheduler/plugin.rb +0 -28
- data/lib/resque_scheduler/server.rb +0 -183
- data/lib/resque_scheduler/util.rb +0 -34
- data/lib/resque_scheduler/version.rb +0 -5
- data/test/redis-test.conf +0 -108
@@ -1,91 +0,0 @@
|
|
1
|
-
# ### Locking the scheduler process
|
2
|
-
#
|
3
|
-
# There are two places in resque-scheduler that need to be synchonized
|
4
|
-
# in order to be able to run redundant scheduler processes while ensuring jobs don't
|
5
|
-
# get queued multiple times when the master process changes.
|
6
|
-
#
|
7
|
-
# 1) Processing the delayed queues (jobs that are created from enqueue_at/enqueue_in, etc)
|
8
|
-
# 2) Processing the scheduled (cron-like) jobs from rufus-scheduler
|
9
|
-
#
|
10
|
-
# Protecting the delayed queues (#1) is relatively easy. A simple SETNX in
|
11
|
-
# redis would suffice. However, protecting the scheduled jobs is trickier
|
12
|
-
# because the clocks on machines could be slightly off or actual firing times
|
13
|
-
# could vary slightly due to load. If scheduler A's clock is slightly ahead
|
14
|
-
# of scheduler B's clock (since they are on different machines), when
|
15
|
-
# scheduler A dies, we need to ensure that scheduler B doesn't queue jobs
|
16
|
-
# that A already queued before it's death. (This all assumes that it is
|
17
|
-
# better to miss a few scheduled jobs than it is to run them multiple times
|
18
|
-
# for the same iteration.)
|
19
|
-
#
|
20
|
-
# To avoid queuing multiple jobs in the case of master fail-over, the master
|
21
|
-
# should remain the master as long as it can rather than a simple SETNX which
|
22
|
-
# would result in the master roll being passed around frequently.
|
23
|
-
#
|
24
|
-
# Locking Scheme:
|
25
|
-
# Each resque-scheduler process attempts to get the master lock via SETNX.
|
26
|
-
# Once obtained, it sets the expiration for 3 minutes (configurable). The
|
27
|
-
# master process continually updates the timeout on the lock key to be 3
|
28
|
-
# minutes in the future in it's loop(s) (see `run`) and when jobs come out of
|
29
|
-
# rufus-scheduler (see `load_schedule_job`). That ensures that a minimum of
|
30
|
-
# 3 minutes must pass since the last queuing operation before a new master is
|
31
|
-
# chosen. If, for whatever reason, the master fails to update the expiration
|
32
|
-
# for 3 minutes, the key expires and the lock is up for grabs. If
|
33
|
-
# miraculously the original master comes back to life, it will realize it is
|
34
|
-
# no longer the master and stop processing jobs.
|
35
|
-
#
|
36
|
-
# The clocks on the scheduler machines can then be up to 3 minutes off from
|
37
|
-
# each other without the risk of queueing the same scheduled job twice during
|
38
|
-
# a master change. The catch is, in the event of a master change, no
|
39
|
-
# scheduled jobs will be queued during those 3 minutes. So, there is a trade
|
40
|
-
# off: the higher the timeout, the less likely scheduled jobs will be fired
|
41
|
-
# twice but greater chances of missing scheduled jobs. The lower the timeout,
|
42
|
-
# less likely jobs will be missed, greater the chances of jobs firing twice. If
|
43
|
-
# you don't care about jobs firing twice or are certain your machines' clocks
|
44
|
-
# are well in sync, a lower timeout is preferable. One thing to keep in mind:
|
45
|
-
# this only effects *scheduled* jobs - delayed jobs will never be lost or
|
46
|
-
# skipped since eventually a master will come online and it will process
|
47
|
-
# everything that is ready (no matter how old it is). Scheduled jobs work
|
48
|
-
# like cron - if you stop cron, no jobs fire while it's stopped and it doesn't
|
49
|
-
# fire jobs that were missed when it starts up again.
|
50
|
-
|
51
|
-
require 'resque/scheduler/lock'
|
52
|
-
|
53
|
-
module Resque
|
54
|
-
module SchedulerLocking
|
55
|
-
def master_lock
|
56
|
-
@master_lock ||= build_master_lock
|
57
|
-
end
|
58
|
-
|
59
|
-
def supports_lua?
|
60
|
-
redis_master_version >= 2.5
|
61
|
-
end
|
62
|
-
|
63
|
-
def is_master?
|
64
|
-
master_lock.acquire! || master_lock.locked?
|
65
|
-
end
|
66
|
-
|
67
|
-
def release_master_lock!
|
68
|
-
master_lock.release!
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
def build_master_lock
|
74
|
-
if supports_lua?
|
75
|
-
Resque::Scheduler::Lock::Resilient.new(master_lock_key)
|
76
|
-
else
|
77
|
-
Resque::Scheduler::Lock::Basic.new(master_lock_key)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def master_lock_key
|
82
|
-
lock_prefix = ENV['RESQUE_SCHEDULER_MASTER_LOCK_PREFIX'] || ''
|
83
|
-
lock_prefix += ':' if lock_prefix != ''
|
84
|
-
"#{Resque.redis.namespace}:#{lock_prefix}resque_scheduler_master_lock"
|
85
|
-
end
|
86
|
-
|
87
|
-
def redis_master_version
|
88
|
-
Resque.redis.info['redis_version'].to_f
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
data/lib/resque_scheduler.rb
DELETED
@@ -1,386 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'resque'
|
3
|
-
require 'resque_scheduler/version'
|
4
|
-
require 'resque_scheduler/util'
|
5
|
-
require 'resque/scheduler'
|
6
|
-
require 'resque_scheduler/plugin'
|
7
|
-
|
8
|
-
module ResqueScheduler
|
9
|
-
autoload :Cli, 'resque_scheduler/cli'
|
10
|
-
|
11
|
-
#
|
12
|
-
# Accepts a new schedule configuration of the form:
|
13
|
-
#
|
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
|
-
# }
|
24
|
-
#
|
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.
|
29
|
-
#
|
30
|
-
# Any jobs that were in the old schedule, but are not
|
31
|
-
# present in the new schedule, will be removed.
|
32
|
-
#
|
33
|
-
# :cron can be any cron scheduling string
|
34
|
-
#
|
35
|
-
# :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage
|
36
|
-
# for valid syntax. If :cron is present it will take precedence over :every.
|
37
|
-
#
|
38
|
-
# :class must be a resque worker class. If it is missing, the job name (hash key)
|
39
|
-
# will be used as :class.
|
40
|
-
#
|
41
|
-
# :args can be any yaml which will be converted to a ruby literal and
|
42
|
-
# passed in a params. (optional)
|
43
|
-
#
|
44
|
-
# :rails_envs is the list of envs where the job gets loaded. Envs are
|
45
|
-
# comma separated (optional)
|
46
|
-
#
|
47
|
-
# :description is just that, a description of the job (optional). If
|
48
|
-
# params is an array, each element in the array is passed as a separate
|
49
|
-
# param, otherwise params is passed in as the only parameter to perform.
|
50
|
-
def schedule=(schedule_hash)
|
51
|
-
# clean the schedules as it exists in redis
|
52
|
-
clean_schedules
|
53
|
-
|
54
|
-
schedule_hash = prepare_schedule(schedule_hash)
|
55
|
-
|
56
|
-
# store all schedules in redis, so we can retrieve them back everywhere.
|
57
|
-
schedule_hash.each do |name, job_spec|
|
58
|
-
set_schedule(name, job_spec)
|
59
|
-
end
|
60
|
-
|
61
|
-
# ensure only return the successfully saved data!
|
62
|
-
reload_schedule!
|
63
|
-
end
|
64
|
-
|
65
|
-
# Returns the schedule hash
|
66
|
-
def schedule
|
67
|
-
@schedule ||= get_schedules
|
68
|
-
if @schedule.nil?
|
69
|
-
return {}
|
70
|
-
end
|
71
|
-
@schedule
|
72
|
-
end
|
73
|
-
|
74
|
-
# reloads the schedule from redis
|
75
|
-
def reload_schedule!
|
76
|
-
@schedule = get_schedules
|
77
|
-
end
|
78
|
-
|
79
|
-
# gets the schedules as it exists in redis
|
80
|
-
def get_schedules
|
81
|
-
unless redis.exists(:schedules)
|
82
|
-
return nil
|
83
|
-
end
|
84
|
-
|
85
|
-
redis.hgetall(:schedules).tap do |h|
|
86
|
-
h.each do |name, config|
|
87
|
-
h[name] = decode(config)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# clean the schedules as it exists in redis, useful for first setup?
|
93
|
-
def clean_schedules
|
94
|
-
if redis.exists(:schedules)
|
95
|
-
redis.hkeys(:schedules).each do |key|
|
96
|
-
remove_schedule(key) if !schedule_persisted?(key)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
@schedule = nil
|
100
|
-
true
|
101
|
-
end
|
102
|
-
|
103
|
-
# Create or update a schedule with the provided name and configuration.
|
104
|
-
#
|
105
|
-
# Note: values for class and custom_job_class need to be strings,
|
106
|
-
# not constants.
|
107
|
-
#
|
108
|
-
# Resque.set_schedule('some_job', {:class => 'SomeJob',
|
109
|
-
# :every => '15mins',
|
110
|
-
# :queue => 'high',
|
111
|
-
# :args => '/tmp/poop'})
|
112
|
-
def set_schedule(name, config)
|
113
|
-
existing_config = get_schedule(name)
|
114
|
-
persist = config.delete(:persist) || config.delete('persist')
|
115
|
-
unless existing_config && existing_config == config
|
116
|
-
redis.pipelined do
|
117
|
-
redis.hset(:schedules, name, encode(config))
|
118
|
-
redis.sadd(:schedules_changed, name)
|
119
|
-
if persist
|
120
|
-
redis.sadd(:persisted_schedules, name)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
config
|
125
|
-
end
|
126
|
-
|
127
|
-
# retrive the schedule configuration for the given name
|
128
|
-
def get_schedule(name)
|
129
|
-
decode(redis.hget(:schedules, name))
|
130
|
-
end
|
131
|
-
|
132
|
-
def schedule_persisted?(name)
|
133
|
-
redis.sismember(:persisted_schedules, name)
|
134
|
-
end
|
135
|
-
|
136
|
-
# remove a given schedule by name
|
137
|
-
def remove_schedule(name)
|
138
|
-
redis.pipelined do
|
139
|
-
redis.hdel(:schedules, name)
|
140
|
-
redis.srem(:persisted_schedules, name)
|
141
|
-
redis.sadd(:schedules_changed, name)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# This method is nearly identical to +enqueue+ only it also
|
146
|
-
# takes a timestamp which will be used to schedule the job
|
147
|
-
# for queueing. Until timestamp is in the past, the job will
|
148
|
-
# sit in the schedule list.
|
149
|
-
def enqueue_at(timestamp, klass, *args)
|
150
|
-
validate(klass)
|
151
|
-
enqueue_at_with_queue(queue_from_class(klass), timestamp, klass, *args)
|
152
|
-
end
|
153
|
-
|
154
|
-
# Identical to +enqueue_at+, except you can also specify
|
155
|
-
# a queue in which the job will be placed after the
|
156
|
-
# timestamp has passed. It respects Resque.inline option, by
|
157
|
-
# creating the job right away instead of adding to the queue.
|
158
|
-
def enqueue_at_with_queue(queue, timestamp, klass, *args)
|
159
|
-
return false unless Plugin.run_before_schedule_hooks(klass, *args)
|
160
|
-
|
161
|
-
if Resque.inline? || timestamp.to_i < Time.now.to_i
|
162
|
-
# Just create the job and let resque perform it right away with inline.
|
163
|
-
# If the class is a custom job class, call self#scheduled on it. This allows you to do things like
|
164
|
-
# Resque.enqueue_at(timestamp, CustomJobClass, :opt1 => val1). Otherwise, pass off to Resque.
|
165
|
-
if klass.respond_to?(:scheduled)
|
166
|
-
klass.scheduled(queue, klass.to_s(), *args)
|
167
|
-
else
|
168
|
-
Resque::Job.create(queue, klass, *args)
|
169
|
-
end
|
170
|
-
else
|
171
|
-
delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
|
172
|
-
end
|
173
|
-
|
174
|
-
Plugin.run_after_schedule_hooks(klass, *args)
|
175
|
-
end
|
176
|
-
|
177
|
-
# Identical to enqueue_at but takes number_of_seconds_from_now
|
178
|
-
# instead of a timestamp.
|
179
|
-
def enqueue_in(number_of_seconds_from_now, klass, *args)
|
180
|
-
enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
|
181
|
-
end
|
182
|
-
|
183
|
-
# Identical to +enqueue_in+, except you can also specify
|
184
|
-
# a queue in which the job will be placed after the
|
185
|
-
# number of seconds has passed.
|
186
|
-
def enqueue_in_with_queue(queue, number_of_seconds_from_now, klass, *args)
|
187
|
-
enqueue_at_with_queue(queue, Time.now + number_of_seconds_from_now, klass, *args)
|
188
|
-
end
|
189
|
-
|
190
|
-
# Used internally to stuff the item into the schedule sorted list.
|
191
|
-
# +timestamp+ can be either in seconds or a datetime object
|
192
|
-
# Insertion if O(log(n)).
|
193
|
-
# Returns true if it's the first job to be scheduled at that time, else false
|
194
|
-
def delayed_push(timestamp, item)
|
195
|
-
# First add this item to the list for this timestamp
|
196
|
-
redis.rpush("delayed:#{timestamp.to_i}", encode(item))
|
197
|
-
|
198
|
-
# Store the timestamps at with this item occurs
|
199
|
-
redis.sadd("timestamps:#{encode(item)}", "delayed:#{timestamp.to_i}")
|
200
|
-
|
201
|
-
# Now, add this timestamp to the zsets. The score and the value are
|
202
|
-
# the same since we'll be querying by timestamp, and we don't have
|
203
|
-
# anything else to store.
|
204
|
-
redis.zadd :delayed_queue_schedule, timestamp.to_i, timestamp.to_i
|
205
|
-
end
|
206
|
-
|
207
|
-
# Returns an array of timestamps based on start and count
|
208
|
-
def delayed_queue_peek(start, count)
|
209
|
-
Array(redis.zrange(:delayed_queue_schedule, start, start+count-1)).collect { |x| x.to_i }
|
210
|
-
end
|
211
|
-
|
212
|
-
# Returns the size of the delayed queue schedule
|
213
|
-
def delayed_queue_schedule_size
|
214
|
-
redis.zcard :delayed_queue_schedule
|
215
|
-
end
|
216
|
-
|
217
|
-
# Returns the number of jobs for a given timestamp in the delayed queue schedule
|
218
|
-
def delayed_timestamp_size(timestamp)
|
219
|
-
redis.llen("delayed:#{timestamp.to_i}").to_i
|
220
|
-
end
|
221
|
-
|
222
|
-
# Returns an array of delayed items for the given timestamp
|
223
|
-
def delayed_timestamp_peek(timestamp, start, count)
|
224
|
-
if 1 == count
|
225
|
-
r = list_range "delayed:#{timestamp.to_i}", start, count
|
226
|
-
r.nil? ? [] : [r]
|
227
|
-
else
|
228
|
-
list_range "delayed:#{timestamp.to_i}", start, count
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
# Returns the next delayed queue timestamp
|
233
|
-
# (don't call directly)
|
234
|
-
def next_delayed_timestamp(at_time=nil)
|
235
|
-
items = redis.zrangebyscore :delayed_queue_schedule, '-inf', (at_time || Time.now).to_i, :limit => [0, 1]
|
236
|
-
timestamp = items.nil? ? nil : Array(items).first
|
237
|
-
timestamp.to_i unless timestamp.nil?
|
238
|
-
end
|
239
|
-
|
240
|
-
# Returns the next item to be processed for a given timestamp, nil if
|
241
|
-
# done. (don't call directly)
|
242
|
-
# +timestamp+ can either be in seconds or a datetime
|
243
|
-
def next_item_for_timestamp(timestamp)
|
244
|
-
key = "delayed:#{timestamp.to_i}"
|
245
|
-
|
246
|
-
encoded_item = redis.lpop(key)
|
247
|
-
redis.srem("timestamps:#{encoded_item}", key)
|
248
|
-
item = decode(encoded_item)
|
249
|
-
|
250
|
-
# If the list is empty, remove it.
|
251
|
-
clean_up_timestamp(key, timestamp)
|
252
|
-
item
|
253
|
-
end
|
254
|
-
|
255
|
-
# Clears all jobs created with enqueue_at or enqueue_in
|
256
|
-
def reset_delayed_queue
|
257
|
-
Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |item|
|
258
|
-
key = "delayed:#{item}"
|
259
|
-
items = redis.lrange(key, 0, -1)
|
260
|
-
redis.pipelined do
|
261
|
-
items.each { |ts_item| redis.del("timestamps:#{ts_item}") }
|
262
|
-
end
|
263
|
-
redis.del key
|
264
|
-
end
|
265
|
-
|
266
|
-
redis.del :delayed_queue_schedule
|
267
|
-
end
|
268
|
-
|
269
|
-
# Given an encoded item, remove it from the delayed_queue
|
270
|
-
def remove_delayed(klass, *args)
|
271
|
-
search = encode(job_to_hash(klass, args))
|
272
|
-
timestamps = redis.smembers("timestamps:#{search}")
|
273
|
-
|
274
|
-
replies = redis.pipelined do
|
275
|
-
timestamps.each do |key|
|
276
|
-
redis.lrem(key, 0, search)
|
277
|
-
redis.srem("timestamps:#{search}", key)
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
(replies.nil? || replies.empty?) ? 0 : replies.each_slice(2).collect { |slice| slice.first }.inject(:+)
|
282
|
-
end
|
283
|
-
|
284
|
-
# Given an encoded item, enqueue it now
|
285
|
-
def enqueue_delayed(klass, *args)
|
286
|
-
hash = job_to_hash(klass, args)
|
287
|
-
remove_delayed(klass, *args).times { Resque::Scheduler.enqueue_from_config(hash) }
|
288
|
-
end
|
289
|
-
|
290
|
-
# Given a block, remove jobs that return true from a block
|
291
|
-
#
|
292
|
-
# This allows for removal of delayed jobs that have arguments matching certain criteria
|
293
|
-
def remove_delayed_selection
|
294
|
-
fail ArgumentError, "Please supply a block" unless block_given?
|
295
|
-
|
296
|
-
destroyed = 0
|
297
|
-
# There is no way to search Redis list entries for a partial match, so we query for all
|
298
|
-
# delayed job tasks and do our matching after decoding the payload data
|
299
|
-
jobs = Resque.redis.keys("delayed:*")
|
300
|
-
jobs.each do |job|
|
301
|
-
index = Resque.redis.llen(job) - 1
|
302
|
-
while index >= 0
|
303
|
-
payload = Resque.redis.lindex(job, index)
|
304
|
-
decoded_payload = decode(payload)
|
305
|
-
if yield(decoded_payload['args'])
|
306
|
-
removed = redis.lrem job, 0, payload
|
307
|
-
destroyed += removed
|
308
|
-
index -= removed
|
309
|
-
else
|
310
|
-
index -= 1
|
311
|
-
end
|
312
|
-
end
|
313
|
-
end
|
314
|
-
destroyed
|
315
|
-
end
|
316
|
-
|
317
|
-
# Given a timestamp and job (klass + args) it removes all instances and
|
318
|
-
# returns the count of jobs removed.
|
319
|
-
#
|
320
|
-
# O(N) where N is the number of jobs scheduled to fire at the given
|
321
|
-
# timestamp
|
322
|
-
def remove_delayed_job_from_timestamp(timestamp, klass, *args)
|
323
|
-
key = "delayed:#{timestamp.to_i}"
|
324
|
-
encoded_job = encode(job_to_hash(klass, args))
|
325
|
-
|
326
|
-
redis.srem("timestamps:#{encoded_job}", key)
|
327
|
-
count = redis.lrem(key, 0, encoded_job)
|
328
|
-
clean_up_timestamp(key, timestamp)
|
329
|
-
|
330
|
-
count
|
331
|
-
end
|
332
|
-
|
333
|
-
def count_all_scheduled_jobs
|
334
|
-
total_jobs = 0
|
335
|
-
Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |timestamp|
|
336
|
-
total_jobs += redis.llen("delayed:#{timestamp}").to_i
|
337
|
-
end
|
338
|
-
total_jobs
|
339
|
-
end
|
340
|
-
|
341
|
-
# Returns delayed jobs schedule timestamp for +klass+, +args+.
|
342
|
-
def scheduled_at(klass, *args)
|
343
|
-
search = encode(job_to_hash(klass, args))
|
344
|
-
redis.smembers("timestamps:#{search}").collect do |key|
|
345
|
-
key.tr('delayed:', '').to_i
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
private
|
350
|
-
|
351
|
-
def job_to_hash(klass, args)
|
352
|
-
{:class => klass.to_s, :args => args, :queue => queue_from_class(klass)}
|
353
|
-
end
|
354
|
-
|
355
|
-
def job_to_hash_with_queue(queue, klass, args)
|
356
|
-
{:class => klass.to_s, :args => args, :queue => queue}
|
357
|
-
end
|
358
|
-
|
359
|
-
def clean_up_timestamp(key, timestamp)
|
360
|
-
# If the list is empty, remove it.
|
361
|
-
|
362
|
-
# Use a watch here to ensure nobody adds jobs to this delayed
|
363
|
-
# queue while we're removing it.
|
364
|
-
redis.watch key
|
365
|
-
if 0 == redis.llen(key).to_i
|
366
|
-
redis.multi do
|
367
|
-
redis.del key
|
368
|
-
redis.zrem :delayed_queue_schedule, timestamp.to_i
|
369
|
-
end
|
370
|
-
else
|
371
|
-
redis.unwatch
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
|
-
def prepare_schedule(schedule_hash)
|
376
|
-
prepared_hash = {}
|
377
|
-
schedule_hash.each do |name, job_spec|
|
378
|
-
job_spec = job_spec.dup
|
379
|
-
job_spec['class'] = name unless job_spec.key?('class') || job_spec.key?(:class)
|
380
|
-
prepared_hash[name] = job_spec
|
381
|
-
end
|
382
|
-
prepared_hash
|
383
|
-
end
|
384
|
-
end
|
385
|
-
|
386
|
-
Resque.extend ResqueScheduler
|