resque_admin-scheduler 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/migrate_to_timestamps_set.rb +16 -0
- data/exe/resque-scheduler +5 -0
- data/lib/resque-scheduler.rb +4 -0
- data/lib/resque/scheduler.rb +447 -0
- data/lib/resque/scheduler/cli.rb +147 -0
- data/lib/resque/scheduler/configuration.rb +73 -0
- data/lib/resque/scheduler/delaying_extensions.rb +324 -0
- data/lib/resque/scheduler/env.rb +89 -0
- data/lib/resque/scheduler/extension.rb +13 -0
- data/lib/resque/scheduler/failure_handler.rb +11 -0
- data/lib/resque/scheduler/lock.rb +4 -0
- data/lib/resque/scheduler/lock/base.rb +61 -0
- data/lib/resque/scheduler/lock/basic.rb +27 -0
- data/lib/resque/scheduler/lock/resilient.rb +78 -0
- data/lib/resque/scheduler/locking.rb +104 -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 +141 -0
- data/lib/resque/scheduler/server.rb +268 -0
- data/lib/resque/scheduler/server/views/delayed.erb +63 -0
- data/lib/resque/scheduler/server/views/delayed_schedules.erb +20 -0
- data/lib/resque/scheduler/server/views/delayed_timestamp.erb +26 -0
- data/lib/resque/scheduler/server/views/requeue-params.erb +23 -0
- data/lib/resque/scheduler/server/views/scheduler.erb +58 -0
- data/lib/resque/scheduler/server/views/search.erb +72 -0
- data/lib/resque/scheduler/server/views/search_form.erb +8 -0
- data/lib/resque/scheduler/signal_handling.rb +40 -0
- data/lib/resque/scheduler/tasks.rb +25 -0
- data/lib/resque/scheduler/util.rb +39 -0
- data/lib/resque/scheduler/version.rb +7 -0
- data/tasks/resque_scheduler.rake +2 -0
- metadata +267 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module Resque
|
6
|
+
module Scheduler
|
7
|
+
CLI_OPTIONS_ENV_MAPPING = {
|
8
|
+
app_name: 'APP_NAME',
|
9
|
+
background: 'BACKGROUND',
|
10
|
+
dynamic: 'DYNAMIC_SCHEDULE',
|
11
|
+
env: 'RAILS_ENV',
|
12
|
+
initializer_path: 'INITIALIZER_PATH',
|
13
|
+
logfile: 'LOGFILE',
|
14
|
+
logformat: 'LOGFORMAT',
|
15
|
+
quiet: 'QUIET',
|
16
|
+
pidfile: 'PIDFILE',
|
17
|
+
poll_sleep_amount: 'RESQUE_SCHEDULER_INTERVAL',
|
18
|
+
verbose: 'VERBOSE'
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
class Cli
|
22
|
+
BANNER = <<-EOF.gsub(/ {6}/, '')
|
23
|
+
Usage: resque-scheduler [options]
|
24
|
+
|
25
|
+
Runs a resque scheduler process directly (rather than via rake).
|
26
|
+
|
27
|
+
EOF
|
28
|
+
OPTIONS = [
|
29
|
+
{
|
30
|
+
args: ['-n', '--app-name [APP_NAME]',
|
31
|
+
'Application name for procline'],
|
32
|
+
callback: ->(options) { ->(n) { options[:app_name] = n } }
|
33
|
+
},
|
34
|
+
{
|
35
|
+
args: ['-B', '--background', 'Run in the background [BACKGROUND]'],
|
36
|
+
callback: ->(options) { ->(b) { options[:background] = b } }
|
37
|
+
},
|
38
|
+
{
|
39
|
+
args: ['-D', '--dynamic-schedule',
|
40
|
+
'Enable dynamic scheduling [DYNAMIC_SCHEDULE]'],
|
41
|
+
callback: ->(options) { ->(d) { options[:dynamic] = d } }
|
42
|
+
},
|
43
|
+
{
|
44
|
+
args: ['-E', '--environment [RAILS_ENV]', 'Environment name'],
|
45
|
+
callback: ->(options) { ->(e) { options[:env] = e } }
|
46
|
+
},
|
47
|
+
{
|
48
|
+
args: ['-I', '--initializer-path [INITIALIZER_PATH]',
|
49
|
+
'Path to optional initializer ruby file'],
|
50
|
+
callback: ->(options) { ->(i) { options[:initializer_path] = i } }
|
51
|
+
},
|
52
|
+
{
|
53
|
+
args: ['-i', '--interval [RESQUE_SCHEDULER_INTERVAL]',
|
54
|
+
'Interval for checking if a scheduled job must run'],
|
55
|
+
callback: ->(options) { ->(i) { options[:poll_sleep_amount] = i } }
|
56
|
+
},
|
57
|
+
{
|
58
|
+
args: ['-l', '--logfile [LOGFILE]', 'Log file name'],
|
59
|
+
callback: ->(options) { ->(l) { options[:logfile] = l } }
|
60
|
+
},
|
61
|
+
{
|
62
|
+
args: ['-F', '--logformat [LOGFORMAT]', 'Log output format'],
|
63
|
+
callback: ->(options) { ->(f) { options[:logformat] = f } }
|
64
|
+
},
|
65
|
+
{
|
66
|
+
args: ['-P', '--pidfile [PIDFILE]', 'PID file name'],
|
67
|
+
callback: ->(options) { ->(p) { options[:pidfile] = p } }
|
68
|
+
},
|
69
|
+
{
|
70
|
+
args: ['-q', '--quiet', 'Run with minimal output [QUIET]'],
|
71
|
+
callback: ->(options) { ->(q) { options[:quiet] = q } }
|
72
|
+
},
|
73
|
+
{
|
74
|
+
args: ['-v', '--verbose', 'Run with verbose output [VERBOSE]'],
|
75
|
+
callback: ->(options) { ->(v) { options[:verbose] = v } }
|
76
|
+
}
|
77
|
+
].freeze
|
78
|
+
|
79
|
+
def self.run!(argv = ARGV, env = ENV)
|
80
|
+
new(argv, env).run!
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(argv = ARGV, env = ENV)
|
84
|
+
@argv = argv
|
85
|
+
@env = env
|
86
|
+
end
|
87
|
+
|
88
|
+
def run!
|
89
|
+
pre_run
|
90
|
+
run_forever
|
91
|
+
end
|
92
|
+
|
93
|
+
def pre_run
|
94
|
+
parse_options
|
95
|
+
pre_setup
|
96
|
+
setup_env
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_options
|
100
|
+
option_parser.parse!(argv.dup)
|
101
|
+
end
|
102
|
+
|
103
|
+
def pre_setup
|
104
|
+
if options[:initializer_path]
|
105
|
+
load options[:initializer_path].to_s.strip
|
106
|
+
else
|
107
|
+
false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def setup_env
|
112
|
+
require_relative 'env'
|
113
|
+
runtime_env.setup
|
114
|
+
end
|
115
|
+
|
116
|
+
def run_forever
|
117
|
+
Resque::Scheduler.run
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
attr_reader :argv, :env
|
123
|
+
|
124
|
+
def runtime_env
|
125
|
+
@runtime_env ||= Resque::Scheduler::Env.new(options)
|
126
|
+
end
|
127
|
+
|
128
|
+
def option_parser
|
129
|
+
OptionParser.new do |opts|
|
130
|
+
opts.banner = BANNER
|
131
|
+
opts.version = Resque::Scheduler::VERSION
|
132
|
+
OPTIONS.each do |opt|
|
133
|
+
opts.on(*opt[:args], &opt[:callback].call(options))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def options
|
139
|
+
@options ||= {}.tap do |o|
|
140
|
+
CLI_OPTIONS_ENV_MAPPING.each do |key, envvar|
|
141
|
+
o[key] = env[envvar] if env.include?(envvar)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
module Scheduler
|
5
|
+
module Configuration
|
6
|
+
# Allows for block-style configuration
|
7
|
+
def configure
|
8
|
+
yield self
|
9
|
+
end
|
10
|
+
|
11
|
+
# Used in `#load_schedule_job`
|
12
|
+
attr_writer :env
|
13
|
+
|
14
|
+
def env
|
15
|
+
return @env if @env
|
16
|
+
@env ||= Rails.env if defined?(Rails) && Rails.respond_to?(:env)
|
17
|
+
@env ||= ENV['RAILS_ENV']
|
18
|
+
@env
|
19
|
+
end
|
20
|
+
|
21
|
+
# If true, logs more stuff...
|
22
|
+
attr_writer :verbose
|
23
|
+
|
24
|
+
def verbose
|
25
|
+
@verbose ||= !!ENV['VERBOSE']
|
26
|
+
end
|
27
|
+
|
28
|
+
# If set, produces no output
|
29
|
+
attr_writer :quiet
|
30
|
+
|
31
|
+
def quiet
|
32
|
+
@quiet ||= !!ENV['QUIET']
|
33
|
+
end
|
34
|
+
|
35
|
+
# If set, will write messages to the file
|
36
|
+
attr_writer :logfile
|
37
|
+
|
38
|
+
def logfile
|
39
|
+
@logfile ||= ENV['LOGFILE']
|
40
|
+
end
|
41
|
+
|
42
|
+
# Sets whether to log in 'text' or 'json'
|
43
|
+
attr_writer :logformat
|
44
|
+
|
45
|
+
def logformat
|
46
|
+
@logformat ||= ENV['LOGFORMAT']
|
47
|
+
end
|
48
|
+
|
49
|
+
# If set, will try to update the schedule in the loop
|
50
|
+
attr_writer :dynamic
|
51
|
+
|
52
|
+
def dynamic
|
53
|
+
@dynamic ||= !!ENV['DYNAMIC_SCHEDULE']
|
54
|
+
end
|
55
|
+
|
56
|
+
# If set, will append the app name to procline
|
57
|
+
attr_writer :app_name
|
58
|
+
|
59
|
+
def app_name
|
60
|
+
@app_name ||= ENV['APP_NAME']
|
61
|
+
end
|
62
|
+
|
63
|
+
# Amount of time in seconds to sleep between polls of the delayed
|
64
|
+
# queue. Defaults to 5
|
65
|
+
attr_writer :poll_sleep_amount
|
66
|
+
|
67
|
+
def poll_sleep_amount
|
68
|
+
@poll_sleep_amount ||=
|
69
|
+
Float(ENV.fetch('RESQUE_SCHEDULER_INTERVAL', '5'))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,324 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
require 'resque'
|
3
|
+
require_relative 'plugin'
|
4
|
+
require_relative '../scheduler'
|
5
|
+
|
6
|
+
module Resque
|
7
|
+
module Scheduler
|
8
|
+
module DelayingExtensions
|
9
|
+
# This method is nearly identical to +enqueue+ only it also
|
10
|
+
# takes a timestamp which will be used to schedule the job
|
11
|
+
# for queueing. Until timestamp is in the past, the job will
|
12
|
+
# sit in the schedule list.
|
13
|
+
def enqueue_at(timestamp, klass, *args)
|
14
|
+
validate(klass)
|
15
|
+
enqueue_at_with_queue(
|
16
|
+
queue_from_class(klass), timestamp, klass, *args
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Identical to +enqueue_at+, except you can also specify
|
21
|
+
# a queue in which the job will be placed after the
|
22
|
+
# timestamp has passed. It respects Resque.inline option, by
|
23
|
+
# creating the job right away instead of adding to the queue.
|
24
|
+
def enqueue_at_with_queue(queue, timestamp, klass, *args)
|
25
|
+
return false unless plugin.run_before_schedule_hooks(klass, *args)
|
26
|
+
|
27
|
+
if Resque.inline? || timestamp.to_i < Time.now.to_i
|
28
|
+
# Just create the job and let resque perform it right away with
|
29
|
+
# inline. If the class is a custom job class, call self#scheduled
|
30
|
+
# on it. This allows you to do things like
|
31
|
+
# Resque.enqueue_at(timestamp, CustomJobClass, :opt1 => val1).
|
32
|
+
# Otherwise, pass off to Resque.
|
33
|
+
if klass.respond_to?(:scheduled)
|
34
|
+
klass.scheduled(queue, klass.to_s, *args)
|
35
|
+
else
|
36
|
+
Resque::Job.create(queue, klass, *args)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
|
40
|
+
end
|
41
|
+
|
42
|
+
plugin.run_after_schedule_hooks(klass, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Identical to enqueue_at but takes number_of_seconds_from_now
|
46
|
+
# instead of a timestamp.
|
47
|
+
def enqueue_in(number_of_seconds_from_now, klass, *args)
|
48
|
+
enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Identical to +enqueue_in+, except you can also specify
|
52
|
+
# a queue in which the job will be placed after the
|
53
|
+
# number of seconds has passed.
|
54
|
+
def enqueue_in_with_queue(queue, number_of_seconds_from_now,
|
55
|
+
klass, *args)
|
56
|
+
enqueue_at_with_queue(queue, Time.now + number_of_seconds_from_now,
|
57
|
+
klass, *args)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Used internally to stuff the item into the schedule sorted list.
|
61
|
+
# +timestamp+ can be either in seconds or a datetime object Insertion
|
62
|
+
# if O(log(n)). Returns true if it's the first job to be scheduled at
|
63
|
+
# that time, else false
|
64
|
+
def delayed_push(timestamp, item)
|
65
|
+
# First add this item to the list for this timestamp
|
66
|
+
redis.rpush("delayed:#{timestamp.to_i}", encode(item))
|
67
|
+
|
68
|
+
# Store the timestamps at with this item occurs
|
69
|
+
redis.sadd("timestamps:#{encode(item)}", "delayed:#{timestamp.to_i}")
|
70
|
+
|
71
|
+
# Now, add this timestamp to the zsets. The score and the value are
|
72
|
+
# the same since we'll be querying by timestamp, and we don't have
|
73
|
+
# anything else to store.
|
74
|
+
redis.zadd :delayed_queue_schedule, timestamp.to_i, timestamp.to_i
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns an array of timestamps based on start and count
|
78
|
+
def delayed_queue_peek(start, count)
|
79
|
+
result = redis.zrange(:delayed_queue_schedule, start,
|
80
|
+
start + count - 1)
|
81
|
+
Array(result).map(&:to_i)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the size of the delayed queue schedule
|
85
|
+
def delayed_queue_schedule_size
|
86
|
+
redis.zcard :delayed_queue_schedule
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the number of jobs for a given timestamp in the delayed queue
|
90
|
+
# schedule
|
91
|
+
def delayed_timestamp_size(timestamp)
|
92
|
+
redis.llen("delayed:#{timestamp.to_i}").to_i
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns an array of delayed items for the given timestamp
|
96
|
+
def delayed_timestamp_peek(timestamp, start, count)
|
97
|
+
if 1 == count
|
98
|
+
r = list_range "delayed:#{timestamp.to_i}", start, count
|
99
|
+
r.nil? ? [] : [r]
|
100
|
+
else
|
101
|
+
list_range "delayed:#{timestamp.to_i}", start, count
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the next delayed queue timestamp
|
106
|
+
# (don't call directly)
|
107
|
+
def next_delayed_timestamp(at_time = nil)
|
108
|
+
search_first_delayed_timestamp_in_range(nil, at_time || Time.now)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the next item to be processed for a given timestamp, nil if
|
112
|
+
# done. (don't call directly)
|
113
|
+
# +timestamp+ can either be in seconds or a datetime
|
114
|
+
def next_item_for_timestamp(timestamp)
|
115
|
+
key = "delayed:#{timestamp.to_i}"
|
116
|
+
|
117
|
+
encoded_item = redis.lpop(key)
|
118
|
+
redis.srem("timestamps:#{encoded_item}", key)
|
119
|
+
item = decode(encoded_item)
|
120
|
+
|
121
|
+
# If the list is empty, remove it.
|
122
|
+
clean_up_timestamp(key, timestamp)
|
123
|
+
item
|
124
|
+
end
|
125
|
+
|
126
|
+
# Clears all jobs created with enqueue_at or enqueue_in
|
127
|
+
def reset_delayed_queue
|
128
|
+
Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |item|
|
129
|
+
key = "delayed:#{item}"
|
130
|
+
items = redis.lrange(key, 0, -1)
|
131
|
+
redis.pipelined do
|
132
|
+
items.each { |ts_item| redis.del("timestamps:#{ts_item}") }
|
133
|
+
end
|
134
|
+
redis.del key
|
135
|
+
end
|
136
|
+
|
137
|
+
redis.del :delayed_queue_schedule
|
138
|
+
end
|
139
|
+
|
140
|
+
# Given an encoded item, remove it from the delayed_queue
|
141
|
+
def remove_delayed(klass, *args)
|
142
|
+
search = encode(job_to_hash(klass, args))
|
143
|
+
remove_delayed_job(search)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Given an encoded item, enqueue it now
|
147
|
+
def enqueue_delayed(klass, *args)
|
148
|
+
hash = job_to_hash(klass, args)
|
149
|
+
remove_delayed(klass, *args).times do
|
150
|
+
Resque::Scheduler.enqueue_from_config(hash)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Given a block, remove jobs that return true from a block
|
155
|
+
#
|
156
|
+
# This allows for removal of delayed jobs that have arguments matching
|
157
|
+
# certain criteria
|
158
|
+
def remove_delayed_selection(klass = nil)
|
159
|
+
raise ArgumentError, 'Please supply a block' unless block_given?
|
160
|
+
|
161
|
+
found_jobs = find_delayed_selection(klass) { |args| yield(args) }
|
162
|
+
found_jobs.reduce(0) do |sum, encoded_job|
|
163
|
+
sum + remove_delayed_job(encoded_job)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Given a block, enqueue jobs now that return true from a block
|
168
|
+
#
|
169
|
+
# This allows for enqueuing of delayed jobs that have arguments matching
|
170
|
+
# certain criteria
|
171
|
+
def enqueue_delayed_selection(klass = nil)
|
172
|
+
raise ArgumentError, 'Please supply a block' unless block_given?
|
173
|
+
|
174
|
+
found_jobs = find_delayed_selection(klass) { |args| yield(args) }
|
175
|
+
found_jobs.reduce(0) do |sum, encoded_job|
|
176
|
+
decoded_job = decode(encoded_job)
|
177
|
+
klass = Util.constantize(decoded_job['class'])
|
178
|
+
sum + enqueue_delayed(klass, *decoded_job['args'])
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Given a block, find jobs that return true from a block
|
183
|
+
#
|
184
|
+
# This allows for finding of delayed jobs that have arguments matching
|
185
|
+
# certain criteria
|
186
|
+
def find_delayed_selection(klass = nil, &block)
|
187
|
+
raise ArgumentError, 'Please supply a block' unless block_given?
|
188
|
+
|
189
|
+
timestamps = redis.zrange(:delayed_queue_schedule, 0, -1)
|
190
|
+
|
191
|
+
# Beyond 100 there's almost no improvement in speed
|
192
|
+
found = timestamps.each_slice(100).map do |ts_group|
|
193
|
+
jobs = redis.pipelined do |r|
|
194
|
+
ts_group.each do |ts|
|
195
|
+
r.lrange("delayed:#{ts}", 0, -1)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
jobs.flatten.select do |payload|
|
200
|
+
payload_matches_selection?(decode(payload), klass, &block)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
found.flatten
|
205
|
+
end
|
206
|
+
|
207
|
+
# Given a timestamp and job (klass + args) it removes all instances and
|
208
|
+
# returns the count of jobs removed.
|
209
|
+
#
|
210
|
+
# O(N) where N is the number of jobs scheduled to fire at the given
|
211
|
+
# timestamp
|
212
|
+
def remove_delayed_job_from_timestamp(timestamp, klass, *args)
|
213
|
+
return 0 if Resque.inline?
|
214
|
+
|
215
|
+
key = "delayed:#{timestamp.to_i}"
|
216
|
+
encoded_job = encode(job_to_hash(klass, args))
|
217
|
+
|
218
|
+
redis.srem("timestamps:#{encoded_job}", key)
|
219
|
+
count = redis.lrem(key, 0, encoded_job)
|
220
|
+
clean_up_timestamp(key, timestamp)
|
221
|
+
|
222
|
+
count
|
223
|
+
end
|
224
|
+
|
225
|
+
def count_all_scheduled_jobs
|
226
|
+
total_jobs = 0
|
227
|
+
Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |ts|
|
228
|
+
total_jobs += redis.llen("delayed:#{ts}").to_i
|
229
|
+
end
|
230
|
+
total_jobs
|
231
|
+
end
|
232
|
+
|
233
|
+
# Discover if a job has been delayed.
|
234
|
+
# Examples
|
235
|
+
# Resque.delayed?(MyJob)
|
236
|
+
# Resque.delayed?(MyJob, id: 1)
|
237
|
+
# Returns true if the job has been delayed
|
238
|
+
def delayed?(klass, *args)
|
239
|
+
!scheduled_at(klass, *args).empty?
|
240
|
+
end
|
241
|
+
|
242
|
+
# Returns delayed jobs schedule timestamp for +klass+, +args+.
|
243
|
+
def scheduled_at(klass, *args)
|
244
|
+
search = encode(job_to_hash(klass, args))
|
245
|
+
redis.smembers("timestamps:#{search}").map do |key|
|
246
|
+
key.tr('delayed:', '').to_i
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def last_enqueued_at(job_name, date)
|
251
|
+
redis.hset('delayed:last_enqueued_at', job_name, date)
|
252
|
+
end
|
253
|
+
|
254
|
+
def get_last_enqueued_at(job_name)
|
255
|
+
redis.hget('delayed:last_enqueued_at', job_name)
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
def job_to_hash(klass, args)
|
261
|
+
{ class: klass.to_s, args: args, queue: queue_from_class(klass) }
|
262
|
+
end
|
263
|
+
|
264
|
+
def job_to_hash_with_queue(queue, klass, args)
|
265
|
+
{ class: klass.to_s, args: args, queue: queue }
|
266
|
+
end
|
267
|
+
|
268
|
+
def remove_delayed_job(encoded_job)
|
269
|
+
return 0 if Resque.inline?
|
270
|
+
|
271
|
+
timestamps = redis.smembers("timestamps:#{encoded_job}")
|
272
|
+
|
273
|
+
replies = redis.pipelined do
|
274
|
+
timestamps.each do |key|
|
275
|
+
redis.lrem(key, 0, encoded_job)
|
276
|
+
redis.srem("timestamps:#{encoded_job}", key)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
return 0 if replies.nil? || replies.empty?
|
281
|
+
replies.each_slice(2).map(&:first).inject(:+)
|
282
|
+
end
|
283
|
+
|
284
|
+
def clean_up_timestamp(key, timestamp)
|
285
|
+
# Use a watch here to ensure nobody adds jobs to this delayed
|
286
|
+
# queue while we're removing it.
|
287
|
+
redis.watch(key) do
|
288
|
+
if redis.llen(key).to_i == 0
|
289
|
+
# If the list is empty, remove it.
|
290
|
+
redis.multi do
|
291
|
+
redis.del(key)
|
292
|
+
redis.zrem(:delayed_queue_schedule, timestamp.to_i)
|
293
|
+
end
|
294
|
+
else
|
295
|
+
redis.redis.unwatch
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def search_first_delayed_timestamp_in_range(start_at, stop_at)
|
301
|
+
start_at = start_at.nil? ? '-inf' : start_at.to_i
|
302
|
+
stop_at = stop_at.nil? ? '+inf' : stop_at.to_i
|
303
|
+
|
304
|
+
items = redis.zrangebyscore(
|
305
|
+
:delayed_queue_schedule, start_at, stop_at,
|
306
|
+
limit: [0, 1]
|
307
|
+
)
|
308
|
+
timestamp = items.nil? ? nil : Array(items).first
|
309
|
+
timestamp.to_i unless timestamp.nil?
|
310
|
+
end
|
311
|
+
|
312
|
+
def payload_matches_selection?(decoded_payload, klass)
|
313
|
+
return false if decoded_payload.nil?
|
314
|
+
job_class = decoded_payload['class']
|
315
|
+
relevant_class = (klass.nil? || klass.to_s == job_class)
|
316
|
+
relevant_class && yield(decoded_payload['args'])
|
317
|
+
end
|
318
|
+
|
319
|
+
def plugin
|
320
|
+
Resque::Scheduler::Plugin
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|