resque-admin-scheduler 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/AUTHORS.md +81 -0
- data/CHANGELOG.md +456 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +6 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +691 -0
- data/Rakefile +21 -0
- data/exe/resque-scheduler +5 -0
- data/lib/resque/scheduler/cli.rb +147 -0
- data/lib/resque/scheduler/configuration.rb +73 -0
- data/lib/resque/scheduler/delaying_extensions.rb +324 -0
- data/lib/resque/scheduler/env.rb +89 -0
- data/lib/resque/scheduler/extension.rb +13 -0
- data/lib/resque/scheduler/failure_handler.rb +11 -0
- data/lib/resque/scheduler/lock/base.rb +61 -0
- data/lib/resque/scheduler/lock/basic.rb +27 -0
- data/lib/resque/scheduler/lock/resilient.rb +78 -0
- data/lib/resque/scheduler/lock.rb +4 -0
- data/lib/resque/scheduler/locking.rb +104 -0
- data/lib/resque/scheduler/logger_builder.rb +72 -0
- data/lib/resque/scheduler/plugin.rb +31 -0
- data/lib/resque/scheduler/scheduling_extensions.rb +141 -0
- data/lib/resque/scheduler/server/views/delayed.erb +63 -0
- data/lib/resque/scheduler/server/views/delayed_schedules.erb +20 -0
- data/lib/resque/scheduler/server/views/delayed_timestamp.erb +26 -0
- data/lib/resque/scheduler/server/views/requeue-params.erb +23 -0
- data/lib/resque/scheduler/server/views/scheduler.erb +58 -0
- data/lib/resque/scheduler/server/views/search.erb +72 -0
- data/lib/resque/scheduler/server/views/search_form.erb +8 -0
- data/lib/resque/scheduler/server.rb +268 -0
- data/lib/resque/scheduler/signal_handling.rb +40 -0
- data/lib/resque/scheduler/tasks.rb +25 -0
- data/lib/resque/scheduler/util.rb +39 -0
- data/lib/resque/scheduler/version.rb +7 -0
- data/lib/resque/scheduler.rb +447 -0
- data/lib/resque-scheduler.rb +4 -0
- data/resque-scheduler.gemspec +60 -0
- metadata +330 -0
data/README.md
ADDED
@@ -0,0 +1,691 @@
|
|
1
|
+
resque-scheduler
|
2
|
+
================
|
3
|
+
|
4
|
+
|
5
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/resque/resque-scheduler.svg)](https://gemnasium.com/github.com/resque/resque-scheduler)
|
6
|
+
[![Gem Version](https://badge.fury.io/rb/resque-scheduler.svg)](https://badge.fury.io/rb/resque-scheduler)
|
7
|
+
[![Build Status](https://travis-ci.org/resque/resque-scheduler.svg?branch=master)](https://travis-ci.org/resque/resque-scheduler)
|
8
|
+
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/sxvf2086v5j0absb/branch/master?svg=true)](https://ci.appveyor.com/project/resque/resque-scheduler/branch/master)
|
9
|
+
[![Code Climate](https://codeclimate.com/github/resque/resque-scheduler/badges/gpa.svg)](https://codeclimate.com/github/resque/resque-scheduler)
|
10
|
+
|
11
|
+
### Description
|
12
|
+
|
13
|
+
Resque-scheduler is an extension to [Resque](http://github.com/resque/resque)
|
14
|
+
that adds support for queueing items in the future.
|
15
|
+
|
16
|
+
Job scheduling is supported in two different way: Recurring (scheduled) and
|
17
|
+
Delayed.
|
18
|
+
|
19
|
+
Scheduled jobs are like cron jobs, recurring on a regular basis. Delayed
|
20
|
+
jobs are resque jobs that you want to run at some point in the future.
|
21
|
+
The syntax is pretty explanatory:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
Resque.enqueue_in(5.days, SendFollowupEmail, argument) # runs a job in 5 days, calling SendFollowupEmail.perform(argument)
|
25
|
+
# or
|
26
|
+
Resque.enqueue_at(5.days.from_now, SomeJob, argument) # runs a job at a specific time, calling SomeJob.perform(argument)
|
27
|
+
```
|
28
|
+
|
29
|
+
### Documentation
|
30
|
+
|
31
|
+
This `README` covers what most people need to know. If you're looking
|
32
|
+
for details on individual methods, you might want to try the
|
33
|
+
[rdoc](http://rdoc.info/github/resque/resque-scheduler/master/frames).
|
34
|
+
|
35
|
+
### Installation
|
36
|
+
|
37
|
+
To install:
|
38
|
+
|
39
|
+
gem install resque-scheduler
|
40
|
+
|
41
|
+
If you use a Gemfile:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
gem 'resque-scheduler'
|
45
|
+
```
|
46
|
+
|
47
|
+
Adding the resque:scheduler rake task:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require 'resque/scheduler/tasks'
|
51
|
+
```
|
52
|
+
|
53
|
+
### Rake integration
|
54
|
+
|
55
|
+
By default, `resque-scheduler` depends on the "resque:setup" rake task.
|
56
|
+
Since you probably already have this task, lets just put our
|
57
|
+
configuration there. `resque-scheduler` pretty much needs to know
|
58
|
+
everything `resque` needs to know.
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
# Resque tasks
|
62
|
+
require 'resque/tasks'
|
63
|
+
require 'resque/scheduler/tasks'
|
64
|
+
|
65
|
+
namespace :resque do
|
66
|
+
task :setup do
|
67
|
+
require 'resque'
|
68
|
+
|
69
|
+
# you probably already have this somewhere
|
70
|
+
Resque.redis = 'localhost:6379'
|
71
|
+
end
|
72
|
+
|
73
|
+
task :setup_schedule => :setup do
|
74
|
+
require 'resque-scheduler'
|
75
|
+
|
76
|
+
# If you want to be able to dynamically change the schedule,
|
77
|
+
# uncomment this line. A dynamic schedule can be updated via the
|
78
|
+
# Resque::Scheduler.set_schedule (and remove_schedule) methods.
|
79
|
+
# When dynamic is set to true, the scheduler process looks for
|
80
|
+
# schedule changes and applies them on the fly.
|
81
|
+
# Note: This feature is only available in >=2.0.0.
|
82
|
+
# Resque::Scheduler.dynamic = true
|
83
|
+
|
84
|
+
# The schedule doesn't need to be stored in a YAML, it just needs to
|
85
|
+
# be a hash. YAML is usually the easiest.
|
86
|
+
Resque.schedule = YAML.load_file('your_resque_schedule.yml')
|
87
|
+
|
88
|
+
# If your schedule already has +queue+ set for each job, you don't
|
89
|
+
# need to require your jobs. This can be an advantage since it's
|
90
|
+
# less code that resque-scheduler needs to know about. But in a small
|
91
|
+
# project, it's usually easier to just include you job classes here.
|
92
|
+
# So, something like this:
|
93
|
+
require 'jobs'
|
94
|
+
end
|
95
|
+
|
96
|
+
task :scheduler => :setup_schedule
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
The scheduler rake task is responsible for both queueing items from the
|
101
|
+
schedule and polling the delayed queue for items ready to be pushed on
|
102
|
+
to the work queues. For obvious reasons, this process never exits.
|
103
|
+
|
104
|
+
``` bash
|
105
|
+
rake resque:scheduler
|
106
|
+
```
|
107
|
+
|
108
|
+
or, if you want to load the environment first:
|
109
|
+
|
110
|
+
``` bash
|
111
|
+
rake environment resque:scheduler
|
112
|
+
```
|
113
|
+
|
114
|
+
|
115
|
+
### Standalone Executable
|
116
|
+
|
117
|
+
The scheduler may also be run via a standalone `resque-scheduler`
|
118
|
+
executable, which will be available once the gem is installed.
|
119
|
+
|
120
|
+
``` bash
|
121
|
+
# Get some help
|
122
|
+
resque-scheduler --help
|
123
|
+
```
|
124
|
+
|
125
|
+
The executable accepts options via option flags as well as via
|
126
|
+
[environment variables](#environment-variables).
|
127
|
+
|
128
|
+
### Environment Variables
|
129
|
+
|
130
|
+
Both the Rake task and standalone executable support the following
|
131
|
+
environment variables:
|
132
|
+
|
133
|
+
* `APP_NAME` - Application name used in procline (`$0`) (default empty)
|
134
|
+
* `BACKGROUND` - [Run in the background](#running-in-the-background) if
|
135
|
+
non-empty (via `Process.daemon`, if supported) (default `false`)
|
136
|
+
* `DYNAMIC_SCHEDULE` - Enables [dynamic scheduling](#dynamic-schedules)
|
137
|
+
if non-empty (default `false`)
|
138
|
+
* `RAILS_ENV` - Environment to use in procline (`$0`) (default empty)
|
139
|
+
* `INITIALIZER_PATH` - Path to a Ruby file that will be loaded *before*
|
140
|
+
requiring `resque` and `resque/scheduler` (default empty).
|
141
|
+
* `RESQUE_SCHEDULER_INTERVAL` - Interval in seconds for checking if a
|
142
|
+
scheduled job must run (coerced with `Kernel#Float()`) (default `5`)
|
143
|
+
* `LOGFILE` - Log file name (default empty, meaning `$stdout`)
|
144
|
+
* `LOGFORMAT` - Log output format to use (either `'text'` or `'json'`,
|
145
|
+
default `'text'`)
|
146
|
+
* `PIDFILE` - If non-empty, write process PID to file (default empty)
|
147
|
+
* `QUIET` - Silence most output if non-empty (equivalent to a level of
|
148
|
+
`MonoLogger::FATAL`, default `false`)
|
149
|
+
* `VERBOSE` - Maximize log verbosity if non-empty (equivalent to a level
|
150
|
+
of `MonoLogger::DEBUG`, default `false`)
|
151
|
+
|
152
|
+
|
153
|
+
### Resque Pool integration
|
154
|
+
|
155
|
+
For normal work with the
|
156
|
+
[resque-pool](https://github.com/nevans/resque-pool) gem, add the
|
157
|
+
following task to wherever tasks are kept, such as
|
158
|
+
`./lib/tasks/resque.rake`:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
task 'resque:pool:setup' do
|
162
|
+
Resque::Pool.after_prefork do |job|
|
163
|
+
Resque.redis.client.reconnect
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
|
169
|
+
### Delayed jobs
|
170
|
+
|
171
|
+
Delayed jobs are one-off jobs that you want to be put into a queue at some point
|
172
|
+
in the future. The classic example is sending email:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
Resque.enqueue_in(
|
176
|
+
5.days,
|
177
|
+
SendFollowUpEmail,
|
178
|
+
user_id: current_user.id
|
179
|
+
)
|
180
|
+
```
|
181
|
+
|
182
|
+
This will store the job for 5 days in the resque delayed queue at which time
|
183
|
+
the scheduler process will pull it from the delayed queue and put it in the
|
184
|
+
appropriate work queue for the given job and it will be processed as soon as
|
185
|
+
a worker is available (just like any other resque job).
|
186
|
+
|
187
|
+
**NOTE**: The job does not fire **exactly** at the time supplied. Rather, once that
|
188
|
+
time is in the past, the job moves from the delayed queue to the actual resque
|
189
|
+
work queue and will be completed as workers are free to process it.
|
190
|
+
|
191
|
+
Also supported is `Resque.enqueue_at` which takes a timestamp to queue the
|
192
|
+
job, and `Resque.enqueue_at_with_queue` which takes both a timestamp and a
|
193
|
+
queue name:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
Resque.enqueue_at_with_queue(
|
197
|
+
'queue_name',
|
198
|
+
5.days.from_now,
|
199
|
+
SendFollowUpEmail,
|
200
|
+
user_id: current_user.id
|
201
|
+
)
|
202
|
+
```
|
203
|
+
|
204
|
+
The delayed queue is stored in redis and is persisted in the same way the
|
205
|
+
standard resque jobs are persisted (redis writing to disk). Delayed jobs differ
|
206
|
+
from scheduled jobs in that if your scheduler process is down or workers are
|
207
|
+
down when a particular job is supposed to be queue, they will simply "catch up"
|
208
|
+
once they are started again. Jobs are guaranteed to run (provided they make it
|
209
|
+
into the delayed queue) after their given `queue_at` time has passed.
|
210
|
+
|
211
|
+
One other thing to note is that insertion into the delayed queue is O(log(n))
|
212
|
+
since the jobs are stored in a redis sorted set (zset). I can't imagine this
|
213
|
+
being an issue for someone since redis is stupidly fast even at log(n), but full
|
214
|
+
disclosure is always best.
|
215
|
+
|
216
|
+
#### Removing Delayed jobs
|
217
|
+
|
218
|
+
If you have the need to cancel a delayed job, you can do like so:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
# after you've enqueued a job like:
|
222
|
+
Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :user_id => current_user.id)
|
223
|
+
# remove the job with exactly the same parameters:
|
224
|
+
Resque.remove_delayed(SendFollowUpEmail, :user_id => current_user.id)
|
225
|
+
```
|
226
|
+
|
227
|
+
If you need to cancel a delayed job based on some matching arguments, but don't wish to specify each argument from when the job was created, you can do like so:
|
228
|
+
|
229
|
+
``` ruby
|
230
|
+
# after you've enqueued a job like:
|
231
|
+
Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :account_id => current_account.id, :user_id => current_user.id)
|
232
|
+
# remove jobs matching just the account:
|
233
|
+
Resque.remove_delayed_selection { |args| args[0]['account_id'] == current_account.id }
|
234
|
+
# or remove jobs matching just the user:
|
235
|
+
Resque.remove_delayed_selection { |args| args[0]['user_id'] == current_user.id }
|
236
|
+
```
|
237
|
+
|
238
|
+
If you need to enqueue immediately a delayed job based on some matching arguments, but don't wish to specify each argument from when the job was created, you can do like so:
|
239
|
+
|
240
|
+
``` ruby
|
241
|
+
# after you've enqueued a job like:
|
242
|
+
Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :account_id => current_account.id, :user_id => current_user.id)
|
243
|
+
# enqueue immediately jobs matching just the account:
|
244
|
+
Resque.enqueue_delayed_selection { |args| args[0]['account_id'] == current_account.id }
|
245
|
+
# or enqueue immediately jobs matching just the user:
|
246
|
+
Resque.enqueue_delayed_selection { |args| args[0]['user_id'] == current_user.id }
|
247
|
+
```
|
248
|
+
|
249
|
+
### Scheduled Jobs (Recurring Jobs)
|
250
|
+
|
251
|
+
Scheduled (or recurring) jobs are logically no different than a standard cron
|
252
|
+
job. They are jobs that run based on a schedule which can be static or dynamic.
|
253
|
+
|
254
|
+
#### Static schedules
|
255
|
+
|
256
|
+
Static schedules are set when `resque-scheduler` starts by passing a schedule file
|
257
|
+
to `resque-scheduler` initialization like this (see *Installation* above for a more complete example):
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
Resque.schedule = YAML.load_file('your_resque_schedule.yml')
|
261
|
+
```
|
262
|
+
|
263
|
+
If a static schedule is not set `resque-scheduler` will issue a "Schedule empty!" warning on
|
264
|
+
startup, but despite that warning setting a static schedule is totally optional. It is possible
|
265
|
+
to use only dynamic schedules (see below).
|
266
|
+
|
267
|
+
The schedule file is a list of Resque job classes with arguments and a
|
268
|
+
schedule frequency (in crontab syntax). The schedule is just a hash, but
|
269
|
+
is usually stored in a YAML like this:
|
270
|
+
|
271
|
+
```yaml
|
272
|
+
CancelAbandonedOrders:
|
273
|
+
cron: "*/5 * * * *"
|
274
|
+
|
275
|
+
queue_documents_for_indexing:
|
276
|
+
cron: "0 0 * * *"
|
277
|
+
# you can use rufus-scheduler "every" syntax in place of cron if you prefer
|
278
|
+
# every: 1h
|
279
|
+
# By default the job name (hash key) will be taken as worker class name.
|
280
|
+
# If you want to have a different job name and class name, provide the 'class' option
|
281
|
+
class: "QueueDocuments"
|
282
|
+
queue: high
|
283
|
+
args:
|
284
|
+
description: "This job queues all content for indexing in solr"
|
285
|
+
|
286
|
+
clear_leaderboards_contributors:
|
287
|
+
cron: "30 6 * * 1"
|
288
|
+
class: "ClearLeaderboards"
|
289
|
+
queue: low
|
290
|
+
args: contributors
|
291
|
+
description: "This job resets the weekly leaderboard for contributions"
|
292
|
+
```
|
293
|
+
|
294
|
+
If you would like to setup a job that is executed manually you can configure like this in your YAML file.
|
295
|
+
|
296
|
+
```yaml
|
297
|
+
ImportOrdersManual:
|
298
|
+
description: 'Import Amazon Orders Manual'
|
299
|
+
custom_job_class: 'AmazonMws::ImportOrdersJob'
|
300
|
+
never: "* * * * *"
|
301
|
+
queue: high
|
302
|
+
description: "This is a manual job for importing orders."
|
303
|
+
args:
|
304
|
+
days_in_arrears: 7
|
305
|
+
```
|
306
|
+
|
307
|
+
The queue value is optional, but if left unspecified resque-scheduler will
|
308
|
+
attempt to get the queue from the job class, which means it needs to be
|
309
|
+
defined. If you're getting "uninitialized constant" errors, you probably
|
310
|
+
need to either set the queue in the schedule or require your jobs in your
|
311
|
+
"resque:setup" rake task.
|
312
|
+
|
313
|
+
You can provide options to "every" or "cron" via Array:
|
314
|
+
|
315
|
+
```yaml
|
316
|
+
clear_leaderboards_moderator:
|
317
|
+
every:
|
318
|
+
- "30s"
|
319
|
+
- :first_in: '120s'
|
320
|
+
class: "CheckDaemon"
|
321
|
+
queue: daemons
|
322
|
+
description: "This job will check Daemon every 30 seconds after 120 seconds after start"
|
323
|
+
```
|
324
|
+
|
325
|
+
IMPORTANT: Rufus `every` syntax will calculate jobs scheduling time starting from the moment of deploy,
|
326
|
+
resulting in resetting schedule time on every deploy, so it's probably a good idea to use it only for
|
327
|
+
frequent jobs (like every 10-30 minutes), otherwise - when you use something like `every 20h` and deploy once-twice per day -
|
328
|
+
it will schedule the job for 20 hours from deploy, resulting in a job to never be run.
|
329
|
+
|
330
|
+
**NOTE**: Six parameter cron's are also supported (as they supported by
|
331
|
+
rufus-scheduler which powers the resque-scheduler process). This allows you
|
332
|
+
to schedule jobs per second (ie: `"30 * * * * *"` would fire a job every 30
|
333
|
+
seconds past the minute).
|
334
|
+
|
335
|
+
A big shout out to [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler)
|
336
|
+
for handling the heavy lifting of the actual scheduling engine.
|
337
|
+
|
338
|
+
#### Dynamic schedules
|
339
|
+
|
340
|
+
Dynamic schedules are programmatically set on a running `resque-scheduler`.
|
341
|
+
All [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler) options are supported
|
342
|
+
when setting schedules.
|
343
|
+
|
344
|
+
Dynamic schedules are not enabled by default. To be able to dynamically set schedules, you
|
345
|
+
must pass the following to `resque-scheduler` initialization (see *Installation* above for a more complete example):
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
Resque::Scheduler.dynamic = true
|
349
|
+
```
|
350
|
+
|
351
|
+
**NOTE**: In order to delete dynamic schedules via `resque-web` in the
|
352
|
+
"Schedule" tab, you must include the `Rack::MethodOverride` middleware (in
|
353
|
+
`config.ru` or equivalent).
|
354
|
+
|
355
|
+
Dynamic schedules allow for greater flexibility than static schedules as they can be set,
|
356
|
+
unset or changed without having to restart `resque-scheduler`. You can specify, if the schedule
|
357
|
+
must survive a resque-scheduler restart or not. This is done by setting the `persist` configuration
|
358
|
+
for the schedule: it is a boolean value, if set the schedule will persist a restart. By default,
|
359
|
+
a schedule will not be persisted.
|
360
|
+
|
361
|
+
The job to be scheduled must be a valid Resque job class.
|
362
|
+
|
363
|
+
For example, suppose you have a SendEmail job which sends emails. The `perform` method of the
|
364
|
+
job receives a string argument with the email subject. To run the SendEmail job every hour
|
365
|
+
starting five minutes from now, you can do:
|
366
|
+
|
367
|
+
```ruby
|
368
|
+
name = 'send_emails'
|
369
|
+
config = {}
|
370
|
+
config[:class] = 'SendEmail'
|
371
|
+
config[:args] = 'POC email subject'
|
372
|
+
config[:every] = ['1h', {first_in: 5.minutes}]
|
373
|
+
config[:persist] = true
|
374
|
+
Resque.set_schedule(name, config)
|
375
|
+
```
|
376
|
+
|
377
|
+
Schedules can later be removed by passing their name to the `remove_schedule` method:
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
name = 'send_emails'
|
381
|
+
Resque.remove_schedule(name)
|
382
|
+
```
|
383
|
+
|
384
|
+
Schedule names are unique; i.e. two dynamic schedules cannot have the same name. If `set_schedule` is
|
385
|
+
passed the name of an existing schedule, that schedule is updated. E.g. if after setting the above schedule
|
386
|
+
we want the job to run every day instead of every hour from now on, we can do:
|
387
|
+
|
388
|
+
```ruby
|
389
|
+
name = 'send_emails'
|
390
|
+
config = {}
|
391
|
+
config[:class] = 'SendEmail'
|
392
|
+
config[:args] = 'POC email subject'
|
393
|
+
config[:every] = '1d'
|
394
|
+
Resque.set_schedule(name, config)
|
395
|
+
```
|
396
|
+
|
397
|
+
#### Time zones
|
398
|
+
|
399
|
+
Note that if you use the cron syntax, this will be interpreted as in the server time zone
|
400
|
+
rather than the `config.time_zone` specified in Rails.
|
401
|
+
|
402
|
+
You can explicitly specify the time zone that rufus-scheduler will use:
|
403
|
+
|
404
|
+
```yaml
|
405
|
+
cron: "30 6 * * 1 Europe/Stockholm"
|
406
|
+
```
|
407
|
+
|
408
|
+
Also note that `config.time_zone` in Rails allows for a shorthand (e.g. "Stockholm")
|
409
|
+
that rufus-scheduler does not accept. If you write code to set the scheduler time zone
|
410
|
+
from the `config.time_zone` value, make sure it's the right format, e.g. with:
|
411
|
+
|
412
|
+
```ruby
|
413
|
+
ActiveSupport::TimeZone.find_tzinfo(Rails.configuration.time_zone).name
|
414
|
+
```
|
415
|
+
|
416
|
+
A future version of resque-scheduler may do this for you.
|
417
|
+
|
418
|
+
#### Hooks
|
419
|
+
|
420
|
+
Similar to the `before_enqueue`- and `after_enqueue`-hooks provided in Resque
|
421
|
+
(>= 1.19.1), your jobs can specify one or more of the following hooks:
|
422
|
+
|
423
|
+
* `before_schedule`: Called with the job args before a job is placed on
|
424
|
+
the delayed queue. If the hook returns `false`, the job will not be placed on
|
425
|
+
the queue.
|
426
|
+
* `after_schedule`: Called with the job args after a job is placed on the
|
427
|
+
delayed queue. Any exception raised propagates up to the code with queued the
|
428
|
+
job.
|
429
|
+
* `before_delayed_enqueue`: Called with the job args after the job has been
|
430
|
+
removed from the delayed queue, but not yet put on a normal queue. It is
|
431
|
+
called before `before_enqueue`-hooks, and on the same job instance as the
|
432
|
+
`before_enqueue`-hooks will be invoked on. Return values are ignored.
|
433
|
+
* `on_enqueue_failure`: Called with the job args and the exception that was raised
|
434
|
+
while enqueueing a job to resque or external application fails. Return
|
435
|
+
values are ignored. For example:
|
436
|
+
|
437
|
+
```ruby
|
438
|
+
Resque::Scheduler.failure_handler = ExceptionHandlerClass
|
439
|
+
```
|
440
|
+
|
441
|
+
#### Support for resque-status (and other custom jobs)
|
442
|
+
|
443
|
+
Some Resque extensions like
|
444
|
+
[resque-status](http://github.com/quirkey/resque-status) use custom job
|
445
|
+
classes with a slightly different API signature. Resque-scheduler isn't
|
446
|
+
trying to support all existing and future custom job classes, instead it
|
447
|
+
supports a schedule flag so you can extend your custom class and make it
|
448
|
+
support scheduled job.
|
449
|
+
|
450
|
+
Let's pretend we have a `JobWithStatus` class called `FakeLeaderboard`
|
451
|
+
|
452
|
+
```ruby
|
453
|
+
class FakeLeaderboard < Resque::JobWithStatus
|
454
|
+
def perform
|
455
|
+
# do something and keep track of the status
|
456
|
+
end
|
457
|
+
end
|
458
|
+
```
|
459
|
+
|
460
|
+
And then a schedule:
|
461
|
+
|
462
|
+
```yaml
|
463
|
+
create_fake_leaderboards:
|
464
|
+
cron: "30 6 * * 1"
|
465
|
+
queue: scoring
|
466
|
+
custom_job_class: "FakeLeaderboard"
|
467
|
+
args:
|
468
|
+
rails_env: demo
|
469
|
+
description: "This job will auto-create leaderboards for our online demo and the status will update as the worker makes progress"
|
470
|
+
```
|
471
|
+
|
472
|
+
If your extension doesn't support scheduled job, you would need to extend the
|
473
|
+
custom job class to support the #scheduled method:
|
474
|
+
|
475
|
+
```ruby
|
476
|
+
module Resque
|
477
|
+
class JobWithStatus
|
478
|
+
# Wrapper API to forward a Resque::Job creation API call into
|
479
|
+
# a JobWithStatus call.
|
480
|
+
def self.scheduled(queue, klass, *args)
|
481
|
+
create(*args)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
```
|
486
|
+
|
487
|
+
### Redundancy and Fail-Over
|
488
|
+
|
489
|
+
*>= 2.0.1 only. Prior to 2.0.1, it is not recommended to run multiple resque-scheduler processes and will result in duplicate jobs.*
|
490
|
+
|
491
|
+
You may want to have resque-scheduler running on multiple machines for
|
492
|
+
redundancy. Electing a master and failover is built in and default. Simply
|
493
|
+
run resque-scheduler on as many machine as you want pointing to the same
|
494
|
+
redis instance and schedule. The scheduler processes will use redis to
|
495
|
+
elect a master process and detect failover when the master dies. Precautions are
|
496
|
+
taken to prevent jobs from potentially being queued twice during failover even
|
497
|
+
when the clocks of the scheduler machines are slightly out of sync (or load affects
|
498
|
+
scheduled job firing time). If you want the gory details, look at Resque::Scheduler::Locking.
|
499
|
+
|
500
|
+
If the scheduler process(es) goes down for whatever reason, the delayed items
|
501
|
+
that should have fired during the outage will fire once the scheduler process
|
502
|
+
is started back up again (regardless of it being on a new machine). Missed
|
503
|
+
scheduled jobs, however, will not fire upon recovery of the scheduler process.
|
504
|
+
Think of scheduled (recurring) jobs as cron jobs - if you stop cron, it doesn't fire
|
505
|
+
missed jobs once it starts back up.
|
506
|
+
|
507
|
+
You might want to share a redis instance amongst multiple Rails applications with different
|
508
|
+
scheduler with different config yaml files. If this is the case, normally, only one will ever
|
509
|
+
run, leading to undesired behaviour. To allow different scheduler configs run at the same time
|
510
|
+
on one redis, you can either namespace your redis connections, or supply an environment variable
|
511
|
+
to split the shared lock key resque-scheduler uses thus:
|
512
|
+
|
513
|
+
``` bash
|
514
|
+
RESQUE_SCHEDULER_MASTER_LOCK_PREFIX=MyApp: rake resque:scheduler
|
515
|
+
```
|
516
|
+
|
517
|
+
### resque-web Additions
|
518
|
+
|
519
|
+
Resque-scheduler also adds to tabs to the resque-web UI. One is for viewing
|
520
|
+
(and manually queueing) the schedule and one is for viewing pending jobs in
|
521
|
+
the delayed queue.
|
522
|
+
|
523
|
+
The Schedule tab:
|
524
|
+
|
525
|
+
![The Schedule Tab](https://f.cloud.github.com/assets/45143/1178456/c99e5568-21b0-11e3-8c57-e1305d0ee8ef.png)
|
526
|
+
|
527
|
+
The Delayed tab:
|
528
|
+
|
529
|
+
![The Delayed Tab](http://img.skitch.com/20100111-ne4fcqtc5emkcuwc5qtais2kwx.jpg)
|
530
|
+
|
531
|
+
#### How do I get the schedule tabs to show up???
|
532
|
+
|
533
|
+
To get these to show up you need to pass a file to `resque-web` to tell it to
|
534
|
+
include the `resque-scheduler` plugin and the resque-schedule server extension
|
535
|
+
to the resque-web sinatra app. Unless you're running redis on localhost, you
|
536
|
+
probably already have this file. It probably looks something like this:
|
537
|
+
|
538
|
+
```ruby
|
539
|
+
require 'resque' # include resque so we can configure it
|
540
|
+
Resque.redis = "redis_server:6379" # tell Resque where redis lives
|
541
|
+
```
|
542
|
+
|
543
|
+
Now, you want to add the following:
|
544
|
+
|
545
|
+
```ruby
|
546
|
+
# This will make the tabs show up.
|
547
|
+
require 'resque-scheduler'
|
548
|
+
require 'resque/scheduler/server'
|
549
|
+
```
|
550
|
+
|
551
|
+
That should make the scheduler tabs show up in `resque-web`.
|
552
|
+
|
553
|
+
#### Changes as of 2.0.0
|
554
|
+
|
555
|
+
As of resque-scheduler 2.0.0, it's no longer necessary to have the resque-web
|
556
|
+
process aware of the schedule because it reads it from redis. But prior to
|
557
|
+
2.0, you'll want to make sure you load the schedule in this file as well.
|
558
|
+
Something like this:
|
559
|
+
|
560
|
+
```ruby
|
561
|
+
Resque.schedule = YAML.load_file(File.join(RAILS_ROOT, 'config/resque_schedule.yml')) # load the schedule
|
562
|
+
```
|
563
|
+
|
564
|
+
Now make sure you're passing that file to resque-web like so:
|
565
|
+
|
566
|
+
resque-web ~/yourapp/config/resque_config.rb
|
567
|
+
|
568
|
+
|
569
|
+
### Running in the background
|
570
|
+
|
571
|
+
(Only supported with ruby >= 1.9). There are scenarios where it's helpful for
|
572
|
+
the resque worker to run itself in the background (usually in combination with
|
573
|
+
PIDFILE). Use the BACKGROUND option so that rake will return as soon as the
|
574
|
+
worker is started.
|
575
|
+
|
576
|
+
$ PIDFILE=./resque-scheduler.pid BACKGROUND=yes \
|
577
|
+
rake resque:scheduler
|
578
|
+
|
579
|
+
|
580
|
+
### Logging
|
581
|
+
|
582
|
+
There are several options to toggle the way scheduler logs its actions. They
|
583
|
+
are toggled by environment variables:
|
584
|
+
|
585
|
+
- `QUIET` will stop logging anything. Completely silent.
|
586
|
+
- `VERBOSE` opposite of 'QUIET'; will log even debug information
|
587
|
+
- `LOGFILE` specifies the file to write logs to. (default standard output)
|
588
|
+
- `LOGFORMAT` specifies either "text" or "json" output format
|
589
|
+
(default "text")
|
590
|
+
|
591
|
+
All of these variables are optional and will be given the following default
|
592
|
+
values:
|
593
|
+
|
594
|
+
```ruby
|
595
|
+
Resque::Scheduler.configure do |c|
|
596
|
+
c.quiet = false
|
597
|
+
c.verbose = false
|
598
|
+
c.logfile = nil # meaning all messages go to $stdout
|
599
|
+
c.logformat = 'text'
|
600
|
+
end
|
601
|
+
```
|
602
|
+
|
603
|
+
### Polling frequency
|
604
|
+
|
605
|
+
You can pass a `RESQUE_SCHEDULER_INTERVAL` option which is an integer or
|
606
|
+
float representing the polling frequency. The default is 5 seconds, but
|
607
|
+
for a semi-active app you may want to use a smaller value.
|
608
|
+
|
609
|
+
$ RESQUE_SCHEDULER_INTERVAL=1 rake resque:scheduler
|
610
|
+
|
611
|
+
**NOTE** This value was previously `INTERVAL` but was renamed to
|
612
|
+
`RESQUE_SCHEDULER_INTERVAL` to avoid clashing with the interval Resque
|
613
|
+
uses for its jobs.
|
614
|
+
|
615
|
+
### Plagiarism alert
|
616
|
+
|
617
|
+
This was intended to be an extension to resque and so resulted in a lot
|
618
|
+
of the code looking very similar to resque, particularly in resque-web
|
619
|
+
and the views. I wanted it to be similar enough that someone familiar
|
620
|
+
with resque could easily work on resque-scheduler.
|
621
|
+
|
622
|
+
### Development
|
623
|
+
|
624
|
+
Working on resque-scheduler requires the following:
|
625
|
+
|
626
|
+
* A relatively modern Ruby interpreter
|
627
|
+
* bundler
|
628
|
+
|
629
|
+
The development setup looks like this, which is roughly the same thing
|
630
|
+
that happens on Travis CI and Appveyor:
|
631
|
+
|
632
|
+
``` bash
|
633
|
+
# Install everything
|
634
|
+
bundle install
|
635
|
+
|
636
|
+
# Make sure tests are green before you change stuff
|
637
|
+
bundle exec rake
|
638
|
+
# Change stuff
|
639
|
+
# Repeat
|
640
|
+
```
|
641
|
+
|
642
|
+
If you have [vagrant](http://www.vagrantup.com) installed, there is a
|
643
|
+
development box available that requires no plugins or external
|
644
|
+
provisioners:
|
645
|
+
|
646
|
+
``` bash
|
647
|
+
vagrant up
|
648
|
+
```
|
649
|
+
|
650
|
+
### Deployment Notes
|
651
|
+
|
652
|
+
It is recommended that a production deployment of `resque-scheduler` be hosted
|
653
|
+
on a dedicated Redis database. While making and managing scheduled tasks,
|
654
|
+
`resque-scheduler` currently scans the entire Redis keyspace, which may cause
|
655
|
+
latency and stability issues if `resque-scheduler` is hosted on a Redis instance
|
656
|
+
storing a large number of keys (such as those written by a different system
|
657
|
+
hosted on the same Redis instance).
|
658
|
+
|
659
|
+
#### Compatibility Notes
|
660
|
+
|
661
|
+
Different versions of the `redis` and `rufus-scheduler` gems are needed
|
662
|
+
depending on your version of `resque-scheduler`. This is typically not a
|
663
|
+
problem with `resque-scheduler` itself, but when mixing dependencies with an
|
664
|
+
existing application.
|
665
|
+
|
666
|
+
This table explains the version requirements for redis gem
|
667
|
+
|
668
|
+
| resque-scheduler | redis gem |
|
669
|
+
|:-----------------|-----------:|
|
670
|
+
| `~> 2.0` | `>= 3.0.0` |
|
671
|
+
| `>= 0.0.1` | `~> 1.3` |
|
672
|
+
|
673
|
+
This table explains the version requirements for rufus-scheduler
|
674
|
+
|
675
|
+
| resque-scheduler | rufus-scheduler |
|
676
|
+
|:-----------------|----------------:|
|
677
|
+
| `~> 4.0` | `~> 3.0` |
|
678
|
+
| `< 4.0` | `~> 2.0` |
|
679
|
+
|
680
|
+
|
681
|
+
### Contributing
|
682
|
+
|
683
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
684
|
+
|
685
|
+
### Authors
|
686
|
+
|
687
|
+
See [AUTHORS.md](AUTHORS.md)
|
688
|
+
|
689
|
+
### License
|
690
|
+
|
691
|
+
See [LICENSE](LICENSE)
|