sidekiq-scheduler 0.3.2 → 0.4.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.
- data/README.md +104 -7
- data/lib/sidekiq-scheduler/capistrano.rb +35 -0
- data/lib/sidekiq-scheduler/cli.rb +8 -1
- data/lib/sidekiq-scheduler/manager.rb +16 -3
- data/lib/sidekiq-scheduler/schedule.rb +125 -0
- data/lib/sidekiq-scheduler/version.rb +1 -1
- data/lib/sidekiq/scheduler.rb +163 -0
- data/test/client_test.rb +12 -9
- data/test/config.yml +1 -0
- data/test/lib/sidekiq/scheduler_test.rb +286 -0
- data/test/manager_test.rb +1 -1
- data/test/schedule_test.rb +124 -0
- data/test/test_helper.rb +34 -2
- data/test/testing_test.rb +2 -2
- metadata +114 -12
data/README.md
CHANGED
@@ -5,22 +5,38 @@
|
|
5
5
|
sidekiq-scheduler is an extension to [Sidekiq](http://github.com/mperham/sidekiq)
|
6
6
|
that adds support for queueing jobs in the future.
|
7
7
|
|
8
|
-
|
9
|
-
is not the intention of this gem. Delayed jobs are Sidekiq jobs that you want to run
|
10
|
-
at some point in the future.
|
8
|
+
This table explains the version requirements for redis
|
11
9
|
|
10
|
+
| sidekiq-scheduler version | required redis version|
|
11
|
+
|:--------------------------|----------------------:|
|
12
|
+
| ~> 1.0.0 | >= 2.2.0 |
|
13
|
+
|
14
|
+
Job scheduling is supported in two different way: Recurring (scheduled) and
|
15
|
+
Delayed.
|
16
|
+
|
17
|
+
Scheduled jobs are like cron jobs, recurring on a regular basis. Delayed
|
18
|
+
jobs are resque jobs that you want to run at some point in the future.
|
12
19
|
The syntax is pretty explanatory:
|
13
20
|
|
14
21
|
MyWorker.perform_in(5.days, 'arg1', 'arg2') # run a job in 5 days
|
15
22
|
# or
|
16
23
|
MyWorker.perform_at(5.days.from_now, 'arg1', 'arg2') # run job at a specific time
|
17
24
|
|
25
|
+
### Documentation
|
26
|
+
|
27
|
+
This README covers what most people need to know. If you're looking for
|
28
|
+
details on individual methods, you might want to try the [rdoc](http://rdoc.info/github/yabawock/sidekiq-scheduler/master/frames).
|
29
|
+
|
30
|
+
|
18
31
|
## Installation
|
19
32
|
|
20
|
-
#
|
33
|
+
#To install:
|
34
|
+
gem install resque-scheduler
|
35
|
+
|
36
|
+
#If you use a Gemfile:
|
21
37
|
gem 'sidekiq-scheduler'
|
22
38
|
|
23
|
-
#
|
39
|
+
#Starting the scheduler
|
24
40
|
bundle exec sidekiq-scheduler
|
25
41
|
|
26
42
|
The scheduler will perform identically to a normal sidekiq worker with
|
@@ -30,9 +46,12 @@ node but all normal configuration options apply.
|
|
30
46
|
|
31
47
|
NOTE: Since it's currently not possible to hook into the default option
|
32
48
|
parsing provided by sidekiq you will need to use a configuration file to
|
33
|
-
override the scheduler options.
|
49
|
+
override the scheduler options.
|
50
|
+
Available options are:
|
34
51
|
|
35
|
-
resolution: <seconds between schedule runs>
|
52
|
+
:resolution: <seconds between schedule runs>
|
53
|
+
:schedule: <the schedule to be run>
|
54
|
+
:dynamic: <if true the schedule can we modified in runtime>
|
36
55
|
|
37
56
|
The scheduling thread will sleep this many seconds between looking for
|
38
57
|
jobs that need moving to the worker queue. The default is 5 seconds
|
@@ -58,6 +77,9 @@ the scheduler will pull it from the delayed queue and put it in the appropriate
|
|
58
77
|
work queue for the given job. It will then be processed as soon as a worker is
|
59
78
|
available (just like any other Sidekiq job).
|
60
79
|
|
80
|
+
The `5.days` syntax will only work if you are using ActiveSupport (Rails). If you
|
81
|
+
are not using Rails, just provide `perform_in` with the number of seconds.
|
82
|
+
|
61
83
|
NOTE: The job does not fire **exactly** at the time supplied. Rather, once that
|
62
84
|
time is in the past, the job moves from the delayed queue to the actual work
|
63
85
|
queue and will be completed as workers are free to process it.
|
@@ -85,6 +107,81 @@ If you have the need to cancel a delayed job, you can do it like this:
|
|
85
107
|
# remove the job with exactly the same parameters:
|
86
108
|
MyWorker.remove_delayed(<timestamp>, 'arg1', 'arg2')
|
87
109
|
|
110
|
+
## Scheduled Jobs (Recurring Jobs)
|
111
|
+
|
112
|
+
Scheduled (or recurring) jobs are logically no different than a standard cron
|
113
|
+
job. They are jobs that run based on a fixed schedule which is set at
|
114
|
+
startup.
|
115
|
+
|
116
|
+
The schedule is a list of Resque worker classes with arguments and a
|
117
|
+
schedule frequency (in crontab syntax). The schedule is just a hash, but
|
118
|
+
is most likely stored in a YAML like so:
|
119
|
+
|
120
|
+
CancelAbandonedOrders:
|
121
|
+
cron: "*/5 * * * *"
|
122
|
+
|
123
|
+
queue_documents_for_indexing:
|
124
|
+
cron: "0 0 * * *"
|
125
|
+
# you can use rufus-scheduler "every" syntax in place of cron if you prefer
|
126
|
+
# every: 1hr
|
127
|
+
# By default the job name (hash key) will be taken as worker class name.
|
128
|
+
# If you want to have a different job name and class name, provide the 'class' option
|
129
|
+
class: QueueDocuments
|
130
|
+
queue: high
|
131
|
+
args:
|
132
|
+
description: "This job queues all content for indexing in solr"
|
133
|
+
|
134
|
+
clear_leaderboards_contributors:
|
135
|
+
cron: "30 6 * * 1"
|
136
|
+
class: ClearLeaderboards
|
137
|
+
queue: low
|
138
|
+
args: contributors
|
139
|
+
description: "This job resets the weekly leaderboard for contributions"
|
140
|
+
|
141
|
+
You can provide options to "every" or "cron" via Array:
|
142
|
+
|
143
|
+
clear_leaderboards_moderator:
|
144
|
+
every: ["30s", :first_in => '120s']
|
145
|
+
class: CheckDaemon
|
146
|
+
queue: daemons
|
147
|
+
description: "This job will check Daemon every 30 seconds after 120 seconds after start"
|
148
|
+
|
149
|
+
|
150
|
+
NOTE: Six parameter cron's are also supported (as they supported by
|
151
|
+
rufus-scheduler which powers the sidekiq-scheduler process). This allows you
|
152
|
+
to schedule jobs per second (ie: "30 * * * * *" would fire a job every 30
|
153
|
+
seconds past the minute).
|
154
|
+
|
155
|
+
A big shout out to [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler)
|
156
|
+
for handling the heavy lifting of the actual scheduling engine.
|
157
|
+
|
158
|
+
### Time zones
|
159
|
+
|
160
|
+
Note that if you use the cron syntax, this will be interpreted as in the server time zone
|
161
|
+
rather than the `config.time_zone` specified in Rails.
|
162
|
+
|
163
|
+
You can explicitly specify the time zone that rufus-scheduler will use:
|
164
|
+
|
165
|
+
cron: "30 6 * * 1 Europe/Stockholm"
|
166
|
+
|
167
|
+
Also note that `config.time_zone` in Rails allows for a shorthand (e.g. "Stockholm")
|
168
|
+
that rufus-scheduler does not accept. If you write code to set the scheduler time zone
|
169
|
+
from the `config.time_zone` value, make sure it's the right format, e.g. with:
|
170
|
+
|
171
|
+
ActiveSupport::TimeZone.find_tzinfo(Rails.configuration.time_zone).name
|
172
|
+
|
173
|
+
A future version of sidekiq-scheduler may do this for you.
|
174
|
+
|
175
|
+
## Using with Testing
|
176
|
+
|
177
|
+
Sidekiq uses a jobs array on workers for testing, which is supported by sidekiq-scheduler when you require the test code:
|
178
|
+
|
179
|
+
require 'sidekiq/testing'
|
180
|
+
require 'sidekiq-scheduler/testing'
|
181
|
+
|
182
|
+
MyWorker.perform_in 5, 'arg1'
|
183
|
+
puts MyWorker.jobs.inspect
|
184
|
+
|
88
185
|
## Note on Patches / Pull Requests
|
89
186
|
|
90
187
|
* Fork the project.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Capistrano::Configuration.instance.load do
|
2
|
+
before "deploy", "sidekiq_scheduler:quiet"
|
3
|
+
after "deploy:stop", "sidekiq_scheduler:stop"
|
4
|
+
after "deploy:start", "sidekiq_scheduler:start"
|
5
|
+
after "deploy:restart", "sidekiq_scheduler:restart"
|
6
|
+
|
7
|
+
_cset(:sidekiq_timeout) { 10 }
|
8
|
+
_cset(:sidekiq_role) { :app }
|
9
|
+
|
10
|
+
namespace :sidekiq_scheduler do
|
11
|
+
|
12
|
+
desc "Quiet sidekiq-scheduler with sidekiq (stop accepting new work)"
|
13
|
+
task :quiet, :roles => lambda { fetch(:sidekiq_role) } do
|
14
|
+
run "cd #{current_path} && if [ -f #{current_path}/tmp/pids/sidekiq.pid ]; then #{fetch(:bundle_cmd, "bundle")} exec sidekiqctl quiet #{current_path}/tmp/pids/sidekiq.pid ; fi"
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Stop sidekiq-scheduler with sidekiq"
|
18
|
+
task :stop, :roles => lambda { fetch(:sidekiq_role) } do
|
19
|
+
run "cd #{current_path} && if [ -f #{current_path}/tmp/pids/sidekiq.pid ]; then #{fetch(:bundle_cmd, "bundle")} exec sidekiqctl stop #{current_path}/tmp/pids/sidekiq.pid #{fetch :sidekiq_timeout} ; fi"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Start sidekiq-scheduler with sidekiq"
|
23
|
+
task :start, :roles => lambda { fetch(:sidekiq_role) } do
|
24
|
+
rails_env = fetch(:rails_env, "production")
|
25
|
+
run "cd #{current_path} ; nohup #{fetch(:bundle_cmd, "bundle")} exec sidekiq-scheduler -e #{rails_env} -C #{current_path}/config/sidekiq.yml -P #{current_path}/tmp/pids/sidekiq.pid >> #{current_path}/log/sidekiq.log 2>&1 &", :pty => false
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Restart sidekiq-scheduler with sidekiq"
|
29
|
+
task :restart, :roles => lambda { fetch(:sidekiq_role) } do
|
30
|
+
stop
|
31
|
+
start
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -11,8 +11,15 @@ module SidekiqScheduler
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def run_scheduler
|
14
|
-
scheduler_options = { :scheduler => true, :resolution => 5 }
|
14
|
+
scheduler_options = { :scheduler => true, :resolution => 5, :schedule => nil }
|
15
15
|
scheduler_options.merge!(options)
|
16
|
+
|
17
|
+
if options[:config_file]
|
18
|
+
file_options = YAML.load_file(options[:config_file])
|
19
|
+
options.merge!(file_options)
|
20
|
+
options.delete(:config_file)
|
21
|
+
end
|
22
|
+
|
16
23
|
scheduler = SidekiqScheduler::Manager.new(scheduler_options)
|
17
24
|
scheduler.start!
|
18
25
|
run_manager
|
@@ -4,6 +4,9 @@ require 'multi_json'
|
|
4
4
|
|
5
5
|
require 'sidekiq/util'
|
6
6
|
|
7
|
+
require 'sidekiq/scheduler'
|
8
|
+
require 'sidekiq-scheduler/schedule'
|
9
|
+
|
7
10
|
module SidekiqScheduler
|
8
11
|
|
9
12
|
##
|
@@ -16,10 +19,11 @@ module SidekiqScheduler
|
|
16
19
|
include Celluloid
|
17
20
|
|
18
21
|
def initialize(options={})
|
19
|
-
logger.info "Booting sidekiq scheduler #{SidekiqScheduler::VERSION} with Redis at #{redis { |r| r.client.location} }"
|
20
|
-
logger.debug { options.inspect }
|
21
22
|
@enabled = options[:scheduler]
|
22
23
|
@resolution = options[:resolution] || 5
|
24
|
+
|
25
|
+
Sidekiq::Scheduler.dynamic = options[:dynamic] || false
|
26
|
+
Sidekiq.schedule = options[:schedule] if options[:schedule]
|
23
27
|
end
|
24
28
|
|
25
29
|
def stop
|
@@ -27,6 +31,14 @@ module SidekiqScheduler
|
|
27
31
|
end
|
28
32
|
|
29
33
|
def start
|
34
|
+
#Load the schedule into rufus
|
35
|
+
#If dynamic is set, load that schedule otherwise use normal load
|
36
|
+
if @enabled && Sidekiq::Scheduler.dynamic
|
37
|
+
Sidekiq::Scheduler.reload_schedule!
|
38
|
+
elsif @enabled
|
39
|
+
Sidekiq::Scheduler.load_schedule!
|
40
|
+
end
|
41
|
+
|
30
42
|
schedule(true)
|
31
43
|
end
|
32
44
|
|
@@ -68,7 +80,8 @@ module SidekiqScheduler
|
|
68
80
|
|
69
81
|
# Dispatch loop
|
70
82
|
loop do
|
71
|
-
|
83
|
+
Sidekiq::Scheduler.update_schedule if Sidekiq::Scheduler.dynamic
|
84
|
+
break unless timestamp = find_next_timestamp
|
72
85
|
find_scheduled_work(timestamp)
|
73
86
|
end
|
74
87
|
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module SidekiqScheduler
|
2
|
+
module Schedule
|
3
|
+
|
4
|
+
# Accepts a new schedule configuration of the form:
|
5
|
+
#
|
6
|
+
# {
|
7
|
+
# "MakeTea" => {
|
8
|
+
# "every" => "1m" },
|
9
|
+
# "some_name" => {
|
10
|
+
# "cron" => "5/* * * *",
|
11
|
+
# "class" => "DoSomeWork",
|
12
|
+
# "args" => "work on this string",
|
13
|
+
# "description" => "this thing works it"s butter off" },
|
14
|
+
# ...
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
# Hash keys can be anything and are used to describe and reference
|
18
|
+
# the scheduled job. If the "class" argument is missing, the key
|
19
|
+
# is used implicitly as "class" argument - in the "MakeTea" example,
|
20
|
+
# "MakeTea" is used both as job name and sidekiq worker class.
|
21
|
+
#
|
22
|
+
# :cron can be any cron scheduling string
|
23
|
+
#
|
24
|
+
# :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage
|
25
|
+
# for valid syntax. If :cron is present it will take precedence over :every.
|
26
|
+
#
|
27
|
+
# :class must be a sidekiq worker class. If it is missing, the job name (hash key)
|
28
|
+
# will be used as :class.
|
29
|
+
#
|
30
|
+
# :args can be any yaml which will be converted to a ruby literal and
|
31
|
+
# passed in a params. (optional)
|
32
|
+
#
|
33
|
+
# :rails_envs is the list of envs where the job gets loaded. Envs are
|
34
|
+
# comma separated (optional)
|
35
|
+
#
|
36
|
+
# :description is just that, a description of the job (optional). If
|
37
|
+
# params is an array, each element in the array is passed as a separate
|
38
|
+
# param, otherwise params is passed in as the only parameter to perform.
|
39
|
+
def schedule=(schedule_hash)
|
40
|
+
schedule_hash = prepare_schedule(schedule_hash)
|
41
|
+
|
42
|
+
if Sidekiq::Scheduler.dynamic
|
43
|
+
schedule_hash.each do |name, job_spec|
|
44
|
+
set_schedule(name, job_spec)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@schedule = schedule_hash
|
48
|
+
end
|
49
|
+
|
50
|
+
def schedule
|
51
|
+
@schedule ||= {}
|
52
|
+
end
|
53
|
+
|
54
|
+
# reloads the schedule from redis
|
55
|
+
def reload_schedule!
|
56
|
+
@schedule = get_schedule
|
57
|
+
end
|
58
|
+
|
59
|
+
# Retrive the schedule configuration for the given name
|
60
|
+
# if the name is nil it returns a hash with all the
|
61
|
+
# names end their schedules.
|
62
|
+
def get_schedule(name = nil)
|
63
|
+
if name.nil?
|
64
|
+
get_all_schedules
|
65
|
+
else
|
66
|
+
encoded_schedule = Sidekiq.redis { |r| r.hget(:schedules, name) }
|
67
|
+
encoded_schedule.nil? ? nil : MultiJson.decode(encoded_schedule)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# gets the schedule as it exists in redis
|
72
|
+
def get_all_schedules
|
73
|
+
schedules = nil
|
74
|
+
if Sidekiq.redis { |r| r.exists(:schedules) }
|
75
|
+
schedules = {}
|
76
|
+
|
77
|
+
Sidekiq.redis { |r| r.hgetall(:schedules) }.tap do |h|
|
78
|
+
h.each do |name, config|
|
79
|
+
schedules[name] = MultiJson.decode(config)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
schedules
|
85
|
+
end
|
86
|
+
|
87
|
+
# Create or update a schedule with the provided name and configuration.
|
88
|
+
#
|
89
|
+
# Note: values for class and custom_job_class need to be strings,
|
90
|
+
# not constants.
|
91
|
+
#
|
92
|
+
# Resque.set_schedule('some_job', {:class => 'SomeJob',
|
93
|
+
# :every => '15mins',
|
94
|
+
# :queue => 'high',
|
95
|
+
# :args => '/tmp/poop'})
|
96
|
+
def set_schedule(name, config)
|
97
|
+
existing_config = get_schedule(name)
|
98
|
+
unless existing_config && existing_config == config
|
99
|
+
Sidekiq.redis { |r| r.hset(:schedules, name, MultiJson.encode(config)) }
|
100
|
+
Sidekiq.redis { |r| r.sadd(:schedules_changed, name) }
|
101
|
+
end
|
102
|
+
config
|
103
|
+
end
|
104
|
+
|
105
|
+
# remove a given schedule by name
|
106
|
+
def remove_schedule(name)
|
107
|
+
Sidekiq.redis { |r| r.hdel(:schedules, name) }
|
108
|
+
Sidekiq.redis { |r| r.sadd(:schedules_changed, name) }
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def prepare_schedule(schedule_hash)
|
114
|
+
prepared_hash = {}
|
115
|
+
schedule_hash.each do |name, job_spec|
|
116
|
+
job_spec = job_spec.dup
|
117
|
+
job_spec['class'] = name unless job_spec.key?('class') || job_spec.key?(:class)
|
118
|
+
prepared_hash[name] = job_spec
|
119
|
+
end
|
120
|
+
prepared_hash
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
Sidekiq.extend SidekiqScheduler::Schedule
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'rufus/scheduler'
|
2
|
+
require 'thwait'
|
3
|
+
require 'sidekiq/util'
|
4
|
+
require 'sidekiq-scheduler/manager'
|
5
|
+
|
6
|
+
module Sidekiq
|
7
|
+
class Scheduler
|
8
|
+
extend Sidekiq::Util
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# If set, will try to update the schulde in the loop
|
12
|
+
attr_accessor :dynamic
|
13
|
+
end
|
14
|
+
|
15
|
+
# the Rufus::Scheduler jobs that are scheduled
|
16
|
+
def self.scheduled_jobs
|
17
|
+
@@scheduled_jobs
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.print_schedule
|
21
|
+
if self.rufus_scheduler
|
22
|
+
logger.info "Scheduling Info\tLast Run"
|
23
|
+
scheduler_jobs = self.rufus_scheduler.all_jobs
|
24
|
+
scheduler_jobs.each do |k, v|
|
25
|
+
logger.info "#{v.t}\t#{v.last}\t"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Pulls the schedule from Sidekiq.schedule and loads it into the
|
31
|
+
# rufus scheduler instance
|
32
|
+
def self.load_schedule!
|
33
|
+
logger.info 'Loading Schedule'
|
34
|
+
|
35
|
+
# Need to load the schedule from redis for the first time if dynamic
|
36
|
+
Sidekiq.reload_schedule! if dynamic
|
37
|
+
|
38
|
+
logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
|
39
|
+
|
40
|
+
@@scheduled_jobs = {}
|
41
|
+
|
42
|
+
Sidekiq.schedule.each do |name, config|
|
43
|
+
self.load_schedule_job(name, config)
|
44
|
+
end
|
45
|
+
|
46
|
+
Sidekiq.redis { |r| r.del(:schedules_changed) }
|
47
|
+
|
48
|
+
logger.info 'Schedules Loaded'
|
49
|
+
end
|
50
|
+
|
51
|
+
# modify interval type value to value with options if options available
|
52
|
+
def self.optionizate_interval_value(value)
|
53
|
+
args = value
|
54
|
+
if args.is_a?(::Array)
|
55
|
+
return args.first if args.size > 2 || !args.last.is_a?(::Hash)
|
56
|
+
# symbolize keys of hash for options
|
57
|
+
args[1] = args[1].inject({}) do |m, i|
|
58
|
+
key, value = i
|
59
|
+
m[(key.to_sym rescue key) || key] = value
|
60
|
+
m
|
61
|
+
end
|
62
|
+
end
|
63
|
+
args
|
64
|
+
end
|
65
|
+
|
66
|
+
# Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
|
67
|
+
def self.load_schedule_job(name, config)
|
68
|
+
# If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
|
69
|
+
# required for the jobs to be scheduled. If rails_env is missing, the
|
70
|
+
# job should be scheduled regardless of what ENV['RAILS_ENV'] is set
|
71
|
+
# to.
|
72
|
+
if config['rails_env'].nil? || self.rails_env_matches?(config)
|
73
|
+
logger.info "Scheduling #{name} "
|
74
|
+
interval_defined = false
|
75
|
+
interval_types = %w{cron every}
|
76
|
+
interval_types.each do |interval_type|
|
77
|
+
if !config[interval_type].nil? && config[interval_type].length > 0
|
78
|
+
args = self.optionizate_interval_value(config[interval_type])
|
79
|
+
|
80
|
+
@@scheduled_jobs[name] = self.rufus_scheduler.send(interval_type, *args) do
|
81
|
+
logger.info "queueing #{config['class']} (#{name})"
|
82
|
+
config.delete(interval_type)
|
83
|
+
self.handle_errors { self.enqueue_from_config(config) }
|
84
|
+
end
|
85
|
+
|
86
|
+
interval_defined = true
|
87
|
+
|
88
|
+
break
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
unless interval_defined
|
93
|
+
logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns true if the given schedule config hash matches the current
|
99
|
+
# ENV['RAILS_ENV']
|
100
|
+
def self.rails_env_matches?(config)
|
101
|
+
config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/,'').split(',').include?(ENV['RAILS_ENV'])
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.handle_errors
|
105
|
+
begin
|
106
|
+
yield
|
107
|
+
rescue Exception => e
|
108
|
+
logger.info "#{e.class.name}: #{e.message}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Enqueue a job based on a config hash
|
113
|
+
def self.enqueue_from_config(job_config)
|
114
|
+
job_config['class'] = constantize(job_config['class']) # Sidekiq expects the class to be constantized.
|
115
|
+
job_config['args'] = '' if job_config['args'].nil?
|
116
|
+
|
117
|
+
Sidekiq::Client.push(job_config)
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.rufus_scheduler
|
121
|
+
@rufus_scheduler ||= Rufus::Scheduler.start_new
|
122
|
+
end
|
123
|
+
|
124
|
+
# Stops old rufus scheduler and creates a new one. Returns the new
|
125
|
+
# rufus scheduler
|
126
|
+
def self.clear_schedule!
|
127
|
+
self.rufus_scheduler.stop
|
128
|
+
@rufus_scheduler = nil
|
129
|
+
@@scheduled_jobs = {}
|
130
|
+
self.rufus_scheduler
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.reload_schedule!
|
134
|
+
logger.info 'Reloading Schedule'
|
135
|
+
self.clear_schedule!
|
136
|
+
self.load_schedule!
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.update_schedule
|
140
|
+
if Sidekiq.redis { |r| r.scard(:schedules_changed) } > 0
|
141
|
+
logger.info 'Updating schedule'
|
142
|
+
Sidekiq.reload_schedule!
|
143
|
+
while schedule_name = Sidekiq.redis { |r| r.spop(:schedules_changed) }
|
144
|
+
if Sidekiq.schedule.keys.include?(schedule_name)
|
145
|
+
self.unschedule_job(schedule_name)
|
146
|
+
self.load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
|
147
|
+
else
|
148
|
+
self.unschedule_job(schedule_name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
logger.info 'Schedules Loaded'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.unschedule_job(name)
|
156
|
+
if self.scheduled_jobs[name]
|
157
|
+
logger.debug "Removing schedule #{name}"
|
158
|
+
self.scheduled_jobs[name].unschedule
|
159
|
+
self.scheduled_jobs.delete(name)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/test/client_test.rb
CHANGED
@@ -4,7 +4,7 @@ require 'timecop'
|
|
4
4
|
class ClientTest < MiniTest::Unit::TestCase
|
5
5
|
describe 'with real redis' do
|
6
6
|
before do
|
7
|
-
Sidekiq.redis =
|
7
|
+
Sidekiq.redis = Sidekiq::RedisConnection.create(:url => 'redis://localhost/15', :namespace => 'testy')
|
8
8
|
Sidekiq.redis {|c| c.flushdb }
|
9
9
|
end
|
10
10
|
|
@@ -82,7 +82,7 @@ class ClientTest < MiniTest::Unit::TestCase
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
describe '
|
85
|
+
describe 'redis calls' do
|
86
86
|
before do
|
87
87
|
@redis = MiniTest::Mock.new
|
88
88
|
def @redis.multi; yield; end
|
@@ -100,9 +100,9 @@ class ClientTest < MiniTest::Unit::TestCase
|
|
100
100
|
end
|
101
101
|
|
102
102
|
it 'pushes delayed messages to redis' do
|
103
|
-
@redis.expect :rpush, 1, ['delayed:
|
104
|
-
@redis.expect :zadd, 1, ['delayed_queue_schedule',
|
105
|
-
Sidekiq::Client.delayed_push('foo',
|
103
|
+
@redis.expect :rpush, 1, ['delayed:1661284491', String]
|
104
|
+
@redis.expect :zadd, 1, ['delayed_queue_schedule', 1661284491, 1661284491]
|
105
|
+
Sidekiq::Client.delayed_push('foo', 1661284491, 'class' => 'Foo', 'args' => [1, 2])
|
106
106
|
@redis.verify
|
107
107
|
end
|
108
108
|
|
@@ -114,17 +114,20 @@ class ClientTest < MiniTest::Unit::TestCase
|
|
114
114
|
end
|
115
115
|
|
116
116
|
it 'handles perform_at' do
|
117
|
-
|
118
|
-
|
117
|
+
|
118
|
+
#@redis.expect :rpush, 1, ['delayed:1331284491', String]
|
119
|
+
@redis.expect :zadd, 1, ['schedule', "1331284491.0", String]
|
119
120
|
MyWorker.perform_at(1331284491, 1, 2)
|
121
|
+
|
120
122
|
@redis.verify
|
121
123
|
end
|
122
124
|
|
123
125
|
it 'handles perform_in' do
|
126
|
+
|
124
127
|
Timecop.freeze(Time.now) do
|
125
128
|
timestamp = Time.now + 30
|
126
|
-
|
127
|
-
@redis.expect :zadd, 1, ['
|
129
|
+
#@redis.expect :rpush, 1, ["delayed:#{timestamp.to_i}", String]
|
130
|
+
@redis.expect :zadd, 1, ['schedule', "#{timestamp.to_f}", String]
|
128
131
|
MyWorker.perform_in(30, 1, 2)
|
129
132
|
@redis.verify
|
130
133
|
end
|
data/test/config.yml
CHANGED
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ManagerTest < MiniTest::Unit::TestCase
|
4
|
+
describe 'Sidekiq::Scheduler' do
|
5
|
+
|
6
|
+
before do
|
7
|
+
Sidekiq::Scheduler.dynamic = false
|
8
|
+
Sidekiq.redis { |r| r.del(:schedules) }
|
9
|
+
Sidekiq.redis { |r| r.del(:schedules_changed) }
|
10
|
+
Sidekiq::Scheduler.clear_schedule!
|
11
|
+
Sidekiq::Scheduler.send(:class_variable_set, :@@scheduled_jobs, {})
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'enqueue constantizes' do
|
15
|
+
# The job should be loaded, since a missing rails_env means ALL envs.
|
16
|
+
ENV['RAILS_ENV'] = 'production'
|
17
|
+
config = {
|
18
|
+
'cron' => '* * * * *',
|
19
|
+
'class' => 'SomeRealClass',
|
20
|
+
'args' => '/tmp'
|
21
|
+
}
|
22
|
+
|
23
|
+
Sidekiq::Client.expects(:push).with(
|
24
|
+
{
|
25
|
+
'cron' => '* * * * *',
|
26
|
+
'class' => SomeRealClass,
|
27
|
+
'args' => '/tmp'
|
28
|
+
}
|
29
|
+
)
|
30
|
+
Sidekiq::Scheduler.enqueue_from_config(config)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'enqueue_from_config respects queue params' do
|
34
|
+
config = {
|
35
|
+
'cron' => '* * * * *',
|
36
|
+
'class' => 'SomeIvarJob',
|
37
|
+
'queue' => 'high'
|
38
|
+
}
|
39
|
+
|
40
|
+
Sidekiq::Client.expects(:push).with(
|
41
|
+
{
|
42
|
+
'cron' => '* * * * *',
|
43
|
+
'class' => SomeIvarJob,
|
44
|
+
'args' => '',
|
45
|
+
'queue' => 'high'
|
46
|
+
}
|
47
|
+
)
|
48
|
+
|
49
|
+
Sidekiq::Scheduler.enqueue_from_config(config)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'config makes it into the rufus_scheduler' do
|
53
|
+
assert_equal(0, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
54
|
+
Sidekiq.schedule = {
|
55
|
+
:some_ivar_job => {
|
56
|
+
'cron' => '* * * * *',
|
57
|
+
'class' => 'SomeIvarJob',
|
58
|
+
'args' => '/tmp'
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
|
63
|
+
Sidekiq::Scheduler.load_schedule!
|
64
|
+
|
65
|
+
assert_equal(1, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
66
|
+
assert Sidekiq::Scheduler.scheduled_jobs.include?(:some_ivar_job)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'can reload schedule' do
|
70
|
+
Sidekiq::Scheduler.dynamic = true
|
71
|
+
Sidekiq.schedule = {
|
72
|
+
:some_ivar_job => {
|
73
|
+
'cron' => '* * * * *',
|
74
|
+
'class' => 'SomeIvarJob',
|
75
|
+
'args' => '/tmp'
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
Sidekiq::Scheduler.load_schedule!
|
80
|
+
|
81
|
+
assert_equal(1, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
82
|
+
assert Sidekiq::Scheduler.scheduled_jobs.include?(:some_ivar_job)
|
83
|
+
|
84
|
+
Sidekiq.redis { |r| r.del(:schedules) }
|
85
|
+
Sidekiq.redis do |r|
|
86
|
+
r.hset(
|
87
|
+
:schedules,
|
88
|
+
'some_ivar_job2',
|
89
|
+
MultiJson.encode(
|
90
|
+
{
|
91
|
+
'cron' => '* * * * *',
|
92
|
+
'class' => 'SomeIvarJob',
|
93
|
+
'args' => '/tmp/2'
|
94
|
+
}
|
95
|
+
)
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
Sidekiq::Scheduler.reload_schedule!
|
100
|
+
|
101
|
+
assert_equal(1, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
102
|
+
|
103
|
+
assert_equal '/tmp/2', Sidekiq.schedule['some_ivar_job2']['args']
|
104
|
+
assert Sidekiq::Scheduler.scheduled_jobs.include?('some_ivar_job2')
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'load_schedule_job loads a schedule' do
|
108
|
+
Sidekiq::Scheduler.load_schedule_job(
|
109
|
+
'some_ivar_job',
|
110
|
+
{
|
111
|
+
'cron' => '* * * * *',
|
112
|
+
'class' => 'SomeIvarJob',
|
113
|
+
'args' => '/tmp'
|
114
|
+
}
|
115
|
+
)
|
116
|
+
|
117
|
+
assert_equal(1, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
118
|
+
assert_equal(1, Sidekiq::Scheduler.scheduled_jobs.size)
|
119
|
+
assert Sidekiq::Scheduler.scheduled_jobs.keys.include?('some_ivar_job')
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'load_schedule_job with every with options' do
|
123
|
+
Sidekiq::Scheduler.load_schedule_job(
|
124
|
+
'some_ivar_job',
|
125
|
+
{
|
126
|
+
'every' => ['30s', {'first_in' => '60s'}],
|
127
|
+
'class' => 'SomeIvarJob',
|
128
|
+
'args' => '/tmp'
|
129
|
+
}
|
130
|
+
)
|
131
|
+
|
132
|
+
assert_equal(1, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
133
|
+
assert_equal(1, Sidekiq::Scheduler.scheduled_jobs.size)
|
134
|
+
assert Sidekiq::Scheduler.scheduled_jobs.keys.include?('some_ivar_job')
|
135
|
+
assert Sidekiq::Scheduler.scheduled_jobs['some_ivar_job'].params.keys.include?(:first_in)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'load_schedule_job with cron with options' do
|
139
|
+
Sidekiq::Scheduler.load_schedule_job(
|
140
|
+
'some_ivar_job',
|
141
|
+
{
|
142
|
+
'cron' => ['* * * * *', {'allow_overlapping' => 'true'}],
|
143
|
+
'class' => 'SomeIvarJob',
|
144
|
+
'args' => '/tmp'
|
145
|
+
}
|
146
|
+
)
|
147
|
+
|
148
|
+
assert_equal(1, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
149
|
+
assert_equal(1, Sidekiq::Scheduler.scheduled_jobs.size)
|
150
|
+
assert Sidekiq::Scheduler.scheduled_jobs.keys.include?('some_ivar_job')
|
151
|
+
assert Sidekiq::Scheduler.scheduled_jobs['some_ivar_job'].params.keys.include?(:allow_overlapping)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'does not load the schedule without cron' do
|
155
|
+
Sidekiq::Scheduler.load_schedule_job(
|
156
|
+
'some_ivar_job',
|
157
|
+
{
|
158
|
+
'class' => 'SomeIvarJob',
|
159
|
+
'args' => '/tmp'
|
160
|
+
}
|
161
|
+
)
|
162
|
+
|
163
|
+
assert_equal(0, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
164
|
+
assert_equal(0, Sidekiq::Scheduler.scheduled_jobs.size)
|
165
|
+
assert !Sidekiq::Scheduler.scheduled_jobs.keys.include?('some_ivar_job')
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'does not load the schedule with an empty cron' do
|
169
|
+
Sidekiq::Scheduler.load_schedule_job(
|
170
|
+
'some_ivar_job',
|
171
|
+
{
|
172
|
+
'cron' => '',
|
173
|
+
'class' => 'SomeIvarJob',
|
174
|
+
'args' => '/tmp'
|
175
|
+
}
|
176
|
+
)
|
177
|
+
|
178
|
+
assert_equal(0, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
179
|
+
assert_equal(0, Sidekiq::Scheduler.scheduled_jobs.size)
|
180
|
+
assert !Sidekiq::Scheduler.scheduled_jobs.keys.include?('some_ivar_job')
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'update_schedule' do
|
184
|
+
Sidekiq::Scheduler.dynamic = true
|
185
|
+
Sidekiq.schedule =
|
186
|
+
{
|
187
|
+
'some_ivar_job' => {'cron' => '* * * * *', 'class' => 'SomeIvarJob', 'args' => '/tmp'},
|
188
|
+
'another_ivar_job' => {'cron' => '* * * * *', 'class' => 'SomeIvarJob', 'args' => '/tmp/5'},
|
189
|
+
'stay_put_job' => {'cron' => '* * * * *', 'class' => 'SomeJob', 'args' => '/tmp'}
|
190
|
+
}
|
191
|
+
|
192
|
+
Sidekiq::Scheduler.load_schedule!
|
193
|
+
|
194
|
+
Sidekiq.set_schedule(
|
195
|
+
'some_ivar_job',
|
196
|
+
{
|
197
|
+
'cron' => '* * * * *',
|
198
|
+
'class' => 'SomeIvarJob',
|
199
|
+
'args' => '/tmp/2'
|
200
|
+
}
|
201
|
+
)
|
202
|
+
Sidekiq.set_schedule(
|
203
|
+
'new_ivar_job',
|
204
|
+
{
|
205
|
+
'cron' => '* * * * *',
|
206
|
+
'class' => 'SomeJob',
|
207
|
+
'args' => '/tmp/3'
|
208
|
+
}
|
209
|
+
)
|
210
|
+
Sidekiq.set_schedule(
|
211
|
+
'stay_put_job',
|
212
|
+
{
|
213
|
+
'cron' => '* * * * *',
|
214
|
+
'class' => 'SomeJob',
|
215
|
+
'args' => '/tmp'
|
216
|
+
}
|
217
|
+
)
|
218
|
+
Sidekiq.remove_schedule('another_ivar_job')
|
219
|
+
|
220
|
+
Sidekiq::Scheduler.update_schedule
|
221
|
+
|
222
|
+
assert_equal(3, Sidekiq::Scheduler.rufus_scheduler.all_jobs.size)
|
223
|
+
assert_equal(3, Sidekiq::Scheduler.scheduled_jobs.size)
|
224
|
+
|
225
|
+
|
226
|
+
%w(some_ivar_job new_ivar_job stay_put_job).each do |job_name|
|
227
|
+
assert Sidekiq::Scheduler.scheduled_jobs.keys.include?(job_name)
|
228
|
+
assert Sidekiq.schedule.keys.include?(job_name)
|
229
|
+
end
|
230
|
+
assert !Sidekiq::Scheduler.scheduled_jobs.keys.include?('another_ivar_job')
|
231
|
+
assert !Sidekiq.schedule.keys.include?('another_ivar_job')
|
232
|
+
assert_equal 0, Sidekiq.redis { |r| r.scard(:schedules_changed) }
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'update_schedule with mocks' do
|
236
|
+
Sidekiq::Scheduler.dynamic = true
|
237
|
+
Sidekiq.schedule = {
|
238
|
+
'some_ivar_job' => {'cron' => '* * * * *', 'class' => 'SomeIvarJob', 'args' => '/tmp'},
|
239
|
+
'another_ivar_job' => {'cron' => '* * * * *', 'class' => 'SomeIvarJob', 'args' => '/tmp/5'},
|
240
|
+
'stay_put_job' => {'cron' => '* * * * *', 'class' => 'SomeJob', 'args' => '/tmp'}
|
241
|
+
}
|
242
|
+
|
243
|
+
Sidekiq::Scheduler.load_schedule!
|
244
|
+
|
245
|
+
Sidekiq::Scheduler.rufus_scheduler.expects(:unschedule).with(Sidekiq::Scheduler.scheduled_jobs['some_ivar_job'].job_id)
|
246
|
+
Sidekiq::Scheduler.rufus_scheduler.expects(:unschedule).with(Sidekiq::Scheduler.scheduled_jobs['another_ivar_job'].job_id)
|
247
|
+
|
248
|
+
Sidekiq.set_schedule(
|
249
|
+
'some_ivar_job',
|
250
|
+
{
|
251
|
+
'cron' => '* * * * *',
|
252
|
+
'class' => 'SomeIvarJob',
|
253
|
+
'args' => '/tmp/2'
|
254
|
+
}
|
255
|
+
)
|
256
|
+
Sidekiq.set_schedule(
|
257
|
+
'new_ivar_job',
|
258
|
+
{
|
259
|
+
'cron' => '* * * * *',
|
260
|
+
'class' => 'SomeJob',
|
261
|
+
'args' => '/tmp/3'
|
262
|
+
}
|
263
|
+
)
|
264
|
+
Sidekiq.set_schedule(
|
265
|
+
'stay_put_job',
|
266
|
+
{
|
267
|
+
'cron' => '* * * * *',
|
268
|
+
'class' => 'SomeJob',
|
269
|
+
'args' => '/tmp'
|
270
|
+
}
|
271
|
+
)
|
272
|
+
Sidekiq.remove_schedule('another_ivar_job')
|
273
|
+
|
274
|
+
Sidekiq::Scheduler.update_schedule
|
275
|
+
|
276
|
+
assert_equal(3, Sidekiq::Scheduler.scheduled_jobs.size)
|
277
|
+
%w(some_ivar_job new_ivar_job stay_put_job).each do |job_name|
|
278
|
+
assert Sidekiq::Scheduler.scheduled_jobs.keys.include?(job_name)
|
279
|
+
assert Sidekiq.schedule.keys.include?(job_name)
|
280
|
+
end
|
281
|
+
assert !Sidekiq::Scheduler.scheduled_jobs.keys.include?('another_ivar_job')
|
282
|
+
assert !Sidekiq.schedule.keys.include?('another_ivar_job')
|
283
|
+
assert_equal 0, Sidekiq.redis { |r| r.scard(:schedules_changed) }
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
data/test/manager_test.rb
CHANGED
@@ -5,7 +5,7 @@ require 'sidekiq/manager'
|
|
5
5
|
class ManagerTest < MiniTest::Unit::TestCase
|
6
6
|
describe 'with redis' do
|
7
7
|
before do
|
8
|
-
Sidekiq.redis =
|
8
|
+
Sidekiq.redis = Sidekiq::RedisConnection.create(:url => 'redis://localhost/15', :namespace => 'testy')
|
9
9
|
Sidekiq.redis {|c| c.flushdb }
|
10
10
|
@scheduler = SidekiqScheduler::Manager.new
|
11
11
|
$processed = 0
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ScheduleTest < MiniTest::Unit::TestCase
|
4
|
+
#class ScheduleManager
|
5
|
+
# extend SidekiqScheduler::ScheduleManager
|
6
|
+
#end
|
7
|
+
|
8
|
+
|
9
|
+
describe 'SidekiqScheduler::Schedule' do
|
10
|
+
it 'schedule= sets the schedule' do
|
11
|
+
Sidekiq::Scheduler.dynamic = true
|
12
|
+
Sidekiq.schedule = {
|
13
|
+
'my_ivar_job' => {
|
14
|
+
'cron' => '* * * * *',
|
15
|
+
'class' => 'SomeIvarJob',
|
16
|
+
'args' => '/tmp/75'
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
assert_equal(
|
21
|
+
{
|
22
|
+
'cron' => '* * * * *',
|
23
|
+
'class' => 'SomeIvarJob',
|
24
|
+
'args' => '/tmp/75'
|
25
|
+
},
|
26
|
+
MultiJson.decode(Sidekiq.redis { |r|
|
27
|
+
r.hget(:schedules, 'my_ivar_job')
|
28
|
+
})
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "schedule= uses job name as 'class' argument if it's missing" do
|
33
|
+
Sidekiq::Scheduler.dynamic = true
|
34
|
+
Sidekiq.schedule = {
|
35
|
+
'SomeIvarJob' => {
|
36
|
+
'cron' => '* * * * *',
|
37
|
+
'args' => '/tmp/75'
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
assert_equal(
|
42
|
+
{
|
43
|
+
'cron' => '* * * * *',
|
44
|
+
'class' => 'SomeIvarJob',
|
45
|
+
'args' => '/tmp/75'
|
46
|
+
},
|
47
|
+
MultiJson.decode(Sidekiq.redis { |r| r.hget(:schedules, 'SomeIvarJob') })
|
48
|
+
)
|
49
|
+
assert_equal('SomeIvarJob', Sidekiq.schedule['SomeIvarJob']['class'])
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'schedule= does not mutate argument' do
|
53
|
+
schedule = {
|
54
|
+
'SomeIvarJob' => {
|
55
|
+
'cron' => '* * * * *',
|
56
|
+
'args' => '/tmp/75'
|
57
|
+
}
|
58
|
+
}
|
59
|
+
Sidekiq.schedule = schedule
|
60
|
+
assert !schedule['SomeIvarJob'].key?('class')
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'set_schedule can set an individual schedule' do
|
64
|
+
Sidekiq.set_schedule(
|
65
|
+
'some_ivar_job',
|
66
|
+
{
|
67
|
+
'cron' => '* * * * *',
|
68
|
+
'class' => 'SomeIvarJob',
|
69
|
+
'args' => '/tmp/22'
|
70
|
+
}
|
71
|
+
)
|
72
|
+
assert_equal(
|
73
|
+
{
|
74
|
+
'cron' => '* * * * *',
|
75
|
+
'class' => 'SomeIvarJob',
|
76
|
+
'args' => '/tmp/22'
|
77
|
+
},
|
78
|
+
MultiJson.decode(Sidekiq.redis { |r| r.hget(:schedules, 'some_ivar_job') })
|
79
|
+
)
|
80
|
+
assert Sidekiq.redis { |r| r.sismember(:schedules_changed, 'some_ivar_job') }
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'get_schedule returns a schedule' do
|
84
|
+
Sidekiq.redis { |r| r.hset(
|
85
|
+
:schedules,
|
86
|
+
'some_ivar_job2',
|
87
|
+
MultiJson.encode(
|
88
|
+
{
|
89
|
+
'cron' => '* * * * *',
|
90
|
+
'class' => 'SomeIvarJob',
|
91
|
+
'args' => '/tmp/33'
|
92
|
+
}
|
93
|
+
)
|
94
|
+
) }
|
95
|
+
assert_equal(
|
96
|
+
{
|
97
|
+
'cron' => '* * * * *',
|
98
|
+
'class' => 'SomeIvarJob',
|
99
|
+
'args' => '/tmp/33'
|
100
|
+
},
|
101
|
+
Sidekiq.get_schedule('some_ivar_job2')
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'remove_schedule removes a schedule' do
|
106
|
+
Sidekiq.redis do |r|
|
107
|
+
r.hset(
|
108
|
+
:schedules,
|
109
|
+
'some_ivar_job3',
|
110
|
+
MultiJson.encode(
|
111
|
+
{
|
112
|
+
'cron' => '* * * * *',
|
113
|
+
'class' => 'SomeIvarJob',
|
114
|
+
'args' => '/tmp/44'
|
115
|
+
}
|
116
|
+
)
|
117
|
+
)
|
118
|
+
end
|
119
|
+
Sidekiq.remove_schedule('some_ivar_job3')
|
120
|
+
assert_equal nil, Sidekiq.redis{ |r| r.hget(:schedules, 'some_ivar_job3') }
|
121
|
+
assert Sidekiq.redis{ |r| r.sismember(:schedules_changed, 'some_ivar_job3') }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -2,13 +2,45 @@ require 'minitest/unit'
|
|
2
2
|
require 'minitest/pride'
|
3
3
|
require 'minitest/autorun'
|
4
4
|
require 'sidekiq-scheduler'
|
5
|
+
require 'mocha'
|
6
|
+
require 'multi_json'
|
7
|
+
require 'mock_redis'
|
5
8
|
|
6
9
|
require 'sidekiq'
|
7
10
|
require 'sidekiq/util'
|
8
|
-
Sidekiq
|
11
|
+
if Sidekiq.respond_to?(:logger)
|
12
|
+
Sidekiq.logger.level = Logger::ERROR
|
13
|
+
else
|
14
|
+
Sidekiq::Util.logger.level = Logger::ERROR
|
15
|
+
end
|
9
16
|
|
10
17
|
# Load support files
|
11
18
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
12
19
|
|
13
20
|
require 'sidekiq/redis_connection'
|
14
|
-
|
21
|
+
|
22
|
+
#Setup redis mock to avoid having a dependency
|
23
|
+
# with redis server during tests
|
24
|
+
$redis = ConnectionPool.new(:timeout => 1, :size => 1) { MockRedis.new }
|
25
|
+
Sidekiq.redis = $redis
|
26
|
+
|
27
|
+
class MiniTest::Spec
|
28
|
+
before :each do
|
29
|
+
$redis.with_connection { |conn| conn.flushdb }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class SomeJob
|
34
|
+
include Sidekiq::Worker
|
35
|
+
def self.perform(repo_id, path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class SomeIvarJob < SomeJob
|
40
|
+
sidekiq_options :queue => :ivar
|
41
|
+
end
|
42
|
+
|
43
|
+
class SomeRealClass
|
44
|
+
include Sidekiq::Worker
|
45
|
+
sidekiq_options :queue => :some_real_queue
|
46
|
+
end
|
data/test/testing_test.rb
CHANGED
@@ -11,7 +11,7 @@ class TestingTest < MiniTest::Unit::TestCase
|
|
11
11
|
assert_equal 0, DirectWorker.jobs.size
|
12
12
|
assert DirectWorker.perform_at(1331759054, 1, 2)
|
13
13
|
assert_equal 1, DirectWorker.jobs.size
|
14
|
-
assert_equal 1331759054, DirectWorker.jobs[0]['
|
14
|
+
assert_equal 1331759054, DirectWorker.jobs[0]['at']
|
15
15
|
DirectWorker.jobs.clear
|
16
16
|
|
17
17
|
# perform_in
|
@@ -20,7 +20,7 @@ class TestingTest < MiniTest::Unit::TestCase
|
|
20
20
|
assert_equal 0, DirectWorker.jobs.size
|
21
21
|
assert DirectWorker.perform_in(30, 1, 2)
|
22
22
|
assert_equal 1, DirectWorker.jobs.size
|
23
|
-
assert_equal timestamp.
|
23
|
+
assert_equal timestamp.to_f, DirectWorker.jobs[0]['at']
|
24
24
|
end
|
25
25
|
ensure
|
26
26
|
# Undo override
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,22 +9,59 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-08-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sidekiq
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
21
|
+
version: '2.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: redis
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.0.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.0.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rufus-scheduler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
25
62
|
- !ruby/object:Gem::Dependency
|
26
63
|
name: rake
|
27
|
-
requirement:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
28
65
|
none: false
|
29
66
|
requirements:
|
30
67
|
- - ! '>='
|
@@ -32,10 +69,15 @@ dependencies:
|
|
32
69
|
version: '0'
|
33
70
|
type: :development
|
34
71
|
prerelease: false
|
35
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
36
78
|
- !ruby/object:Gem::Dependency
|
37
79
|
name: timecop
|
38
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
39
81
|
none: false
|
40
82
|
requirements:
|
41
83
|
- - ! '>='
|
@@ -43,7 +85,60 @@ dependencies:
|
|
43
85
|
version: '0'
|
44
86
|
type: :development
|
45
87
|
prerelease: false
|
46
|
-
version_requirements:
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: mocha
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: minitest
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: mock_redis
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
47
142
|
description: Light weight job scheduling extension for Sidekiq that adds support for
|
48
143
|
queueing items in the future.
|
49
144
|
email:
|
@@ -54,9 +149,12 @@ extensions: []
|
|
54
149
|
extra_rdoc_files: []
|
55
150
|
files:
|
56
151
|
- bin/sidekiq-scheduler
|
152
|
+
- lib/sidekiq/scheduler.rb
|
153
|
+
- lib/sidekiq-scheduler/capistrano.rb
|
57
154
|
- lib/sidekiq-scheduler/cli.rb
|
58
155
|
- lib/sidekiq-scheduler/client.rb
|
59
156
|
- lib/sidekiq-scheduler/manager.rb
|
157
|
+
- lib/sidekiq-scheduler/schedule.rb
|
60
158
|
- lib/sidekiq-scheduler/testing.rb
|
61
159
|
- lib/sidekiq-scheduler/version.rb
|
62
160
|
- lib/sidekiq-scheduler/worker.rb
|
@@ -69,7 +167,9 @@ files:
|
|
69
167
|
- test/client_test.rb
|
70
168
|
- test/config.yml
|
71
169
|
- test/fake_env.rb
|
170
|
+
- test/lib/sidekiq/scheduler_test.rb
|
72
171
|
- test/manager_test.rb
|
172
|
+
- test/schedule_test.rb
|
73
173
|
- test/support/direct_worker.rb
|
74
174
|
- test/support/my_worker.rb
|
75
175
|
- test/test_helper.rb
|
@@ -88,7 +188,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
88
188
|
version: '0'
|
89
189
|
segments:
|
90
190
|
- 0
|
91
|
-
hash:
|
191
|
+
hash: -1906038753253799220
|
92
192
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
193
|
none: false
|
94
194
|
requirements:
|
@@ -97,10 +197,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
197
|
version: '0'
|
98
198
|
segments:
|
99
199
|
- 0
|
100
|
-
hash:
|
200
|
+
hash: -1906038753253799220
|
101
201
|
requirements: []
|
102
202
|
rubyforge_project:
|
103
|
-
rubygems_version: 1.8.
|
203
|
+
rubygems_version: 1.8.23
|
104
204
|
signing_key:
|
105
205
|
specification_version: 3
|
106
206
|
summary: Light weight job scheduling extension for Sidekiq
|
@@ -109,7 +209,9 @@ test_files:
|
|
109
209
|
- test/client_test.rb
|
110
210
|
- test/config.yml
|
111
211
|
- test/fake_env.rb
|
212
|
+
- test/lib/sidekiq/scheduler_test.rb
|
112
213
|
- test/manager_test.rb
|
214
|
+
- test/schedule_test.rb
|
113
215
|
- test/support/direct_worker.rb
|
114
216
|
- test/support/my_worker.rb
|
115
217
|
- test/test_helper.rb
|