sidekiq-scheduler 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|