istox-resque-scheduler 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/AUTHORS.md +87 -0
  3. data/CHANGELOG.md +478 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/CONTRIBUTING.md +6 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +23 -0
  8. data/README.md +698 -0
  9. data/Rakefile +21 -0
  10. data/exe/resque-scheduler +5 -0
  11. data/istox-resque-scheduler.gemspec +61 -0
  12. data/lib/resque-scheduler.rb +4 -0
  13. data/lib/resque/scheduler.rb +460 -0
  14. data/lib/resque/scheduler/cli.rb +147 -0
  15. data/lib/resque/scheduler/configuration.rb +73 -0
  16. data/lib/resque/scheduler/delaying_extensions.rb +356 -0
  17. data/lib/resque/scheduler/env.rb +89 -0
  18. data/lib/resque/scheduler/extension.rb +13 -0
  19. data/lib/resque/scheduler/failure_handler.rb +11 -0
  20. data/lib/resque/scheduler/lock.rb +4 -0
  21. data/lib/resque/scheduler/lock/base.rb +61 -0
  22. data/lib/resque/scheduler/lock/basic.rb +27 -0
  23. data/lib/resque/scheduler/lock/resilient.rb +78 -0
  24. data/lib/resque/scheduler/locking.rb +104 -0
  25. data/lib/resque/scheduler/logger_builder.rb +72 -0
  26. data/lib/resque/scheduler/plugin.rb +31 -0
  27. data/lib/resque/scheduler/scheduling_extensions.rb +142 -0
  28. data/lib/resque/scheduler/server.rb +268 -0
  29. data/lib/resque/scheduler/server/views/delayed.erb +63 -0
  30. data/lib/resque/scheduler/server/views/delayed_schedules.erb +20 -0
  31. data/lib/resque/scheduler/server/views/delayed_timestamp.erb +26 -0
  32. data/lib/resque/scheduler/server/views/requeue-params.erb +23 -0
  33. data/lib/resque/scheduler/server/views/scheduler.erb +58 -0
  34. data/lib/resque/scheduler/server/views/search.erb +72 -0
  35. data/lib/resque/scheduler/server/views/search_form.erb +8 -0
  36. data/lib/resque/scheduler/signal_handling.rb +40 -0
  37. data/lib/resque/scheduler/tasks.rb +25 -0
  38. data/lib/resque/scheduler/util.rb +39 -0
  39. data/lib/resque/scheduler/version.rb +7 -0
  40. metadata +343 -0
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at resque@librelist.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
@@ -0,0 +1,6 @@
1
+ Resque Scheduler contribution guidelines
2
+ ========================================
3
+
4
+ - Pull requests welcome!
5
+ - Please add yourself to [AUTHORS.md](AUTHORS.md) alphabetically by
6
+ first name.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # vim:fileencoding=utf-8
2
+ source 'https://rubygems.org'
3
+
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2014 Ben VandenBos
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
@@ -0,0 +1,698 @@
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 ways: 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 cancel a delayed job based on some matching arguments AND by which class the job is, 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
+ # remove jobs matching just the account and that were of the class SendFollowUpEmail:
244
+ Resque.remove_delayed_selection(SendFollowUpEmail) { |args| args[0]['account_id'] == current_account.id }
245
+ # or remove jobs matching just the user and that were of the class SendFollowUpEmail:
246
+ Resque.remove_delayed_selection(SendFollowUpEmail) { |args| args[0]['user_id'] == current_user.id }
247
+ ```
248
+
249
+ 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:
250
+
251
+ ``` ruby
252
+ # after you've enqueued a job like:
253
+ Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :account_id => current_account.id, :user_id => current_user.id)
254
+ # enqueue immediately jobs matching just the account:
255
+ Resque.enqueue_delayed_selection { |args| args[0]['account_id'] == current_account.id }
256
+ # or enqueue immediately jobs matching just the user:
257
+ Resque.enqueue_delayed_selection { |args| args[0]['user_id'] == current_user.id }
258
+ ```
259
+
260
+ ### Scheduled Jobs (Recurring Jobs)
261
+
262
+ Scheduled (or recurring) jobs are logically no different than a standard cron
263
+ job. They are jobs that run based on a schedule which can be static or dynamic.
264
+
265
+ #### Static schedules
266
+
267
+ Static schedules are set when `resque-scheduler` starts by passing a schedule file
268
+ to `resque-scheduler` initialization like this (see *Installation* above for a more complete example):
269
+
270
+ ```ruby
271
+ Resque.schedule = YAML.load_file('your_resque_schedule.yml')
272
+ ```
273
+
274
+ If a static schedule is not set `resque-scheduler` will issue a "Schedule empty!" warning on
275
+ startup, but despite that warning setting a static schedule is totally optional. It is possible
276
+ to use only dynamic schedules (see below).
277
+
278
+ The schedule file is a list of Resque job classes with arguments and a
279
+ schedule frequency (in crontab syntax). The schedule is just a hash, but
280
+ is usually stored in a YAML like this:
281
+
282
+ ```yaml
283
+ CancelAbandonedOrders:
284
+ cron: "*/5 * * * *"
285
+
286
+ queue_documents_for_indexing:
287
+ cron: "0 0 * * *"
288
+ # you can use rufus-scheduler "every" syntax in place of cron if you prefer
289
+ # every: 1h
290
+ # By default the job name (hash key) will be taken as worker class name.
291
+ # If you want to have a different job name and class name, provide the 'class' option
292
+ class: "QueueDocuments"
293
+ queue: high
294
+ args:
295
+ description: "This job queues all content for indexing in solr"
296
+
297
+ clear_leaderboards_contributors:
298
+ cron: "30 6 * * 1"
299
+ class: "ClearLeaderboards"
300
+ queue: low
301
+ args: contributors
302
+ description: "This job resets the weekly leaderboard for contributions"
303
+ ```
304
+
305
+ If you would like to setup a job that is executed manually you can configure like this in your YAML file.
306
+
307
+ ```yaml
308
+ ImportOrdersManual:
309
+ description: 'Import Amazon Orders Manual'
310
+ custom_job_class: 'AmazonMws::ImportOrdersJob'
311
+ never: "* * * * *"
312
+ queue: high
313
+ description: "This is a manual job for importing orders."
314
+ args:
315
+ days_in_arrears: 7
316
+ ```
317
+
318
+ The queue value is optional, but if left unspecified resque-scheduler will
319
+ attempt to get the queue from the job class, which means it needs to be
320
+ defined. If you're getting "uninitialized constant" errors, you probably
321
+ need to either set the queue in the schedule or require your jobs in your
322
+ "resque:setup" rake task.
323
+
324
+ You can provide options to "every" or "cron" via Array:
325
+
326
+ ```yaml
327
+ clear_leaderboards_moderator:
328
+ every:
329
+ - "30s"
330
+ - :first_in: '120s'
331
+ class: "CheckDaemon"
332
+ queue: daemons
333
+ description: "This job will check Daemon every 30 seconds after 120 seconds after start"
334
+ ```
335
+
336
+ IMPORTANT: Rufus `every` syntax will calculate jobs scheduling time starting from the moment of deploy,
337
+ resulting in resetting schedule time on every deploy, so it's probably a good idea to use it only for
338
+ frequent jobs (like every 10-30 minutes), otherwise - when you use something like `every 20h` and deploy once-twice per day -
339
+ it will schedule the job for 20 hours from deploy, resulting in a job to never be run.
340
+
341
+ **NOTE**: Six parameter cron's are also supported (as they supported by
342
+ rufus-scheduler which powers the resque-scheduler process). This allows you
343
+ to schedule jobs per second (ie: `"30 * * * * *"` would fire a job every 30
344
+ seconds past the minute).
345
+
346
+ A big shout out to [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler)
347
+ for handling the heavy lifting of the actual scheduling engine.
348
+
349
+ #### Dynamic schedules
350
+
351
+ Dynamic schedules are programmatically set on a running `resque-scheduler`.
352
+ Most [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler) options are supported
353
+ when setting schedules. Specifically the `overlap` option will not work.
354
+
355
+ Dynamic schedules are not enabled by default. To be able to dynamically set schedules, you
356
+ must pass the following to `resque-scheduler` initialization (see *Installation* above for a more complete example):
357
+
358
+ ```ruby
359
+ Resque::Scheduler.dynamic = true
360
+ ```
361
+
362
+ **NOTE**: In order to delete dynamic schedules via `resque-web` in the
363
+ "Schedule" tab, you must include the `Rack::MethodOverride` middleware (in
364
+ `config.ru` or equivalent).
365
+
366
+ Dynamic schedules allow for greater flexibility than static schedules as they can be set,
367
+ unset or changed without having to restart `resque-scheduler`. You can specify, if the schedule
368
+ must survive a resque-scheduler restart or not. This is done by setting the `persist` configuration
369
+ for the schedule: it is a boolean value, if set the schedule will persist a restart. By default,
370
+ a schedule will not be persisted.
371
+
372
+ The job to be scheduled must be a valid Resque job class.
373
+
374
+ For example, suppose you have a SendEmail job which sends emails. The `perform` method of the
375
+ job receives a string argument with the email subject. To run the SendEmail job every hour
376
+ starting five minutes from now, you can do:
377
+
378
+ ```ruby
379
+ name = 'send_emails'
380
+ config = {}
381
+ config[:class] = 'SendEmail'
382
+ config[:args] = 'POC email subject'
383
+ config[:every] = ['1h', {first_in: 5.minutes}]
384
+ config[:persist] = true
385
+ Resque.set_schedule(name, config)
386
+ ```
387
+
388
+ Schedules can later be removed by passing their name to the `remove_schedule` method:
389
+
390
+ ```ruby
391
+ name = 'send_emails'
392
+ Resque.remove_schedule(name)
393
+ ```
394
+
395
+ Schedule names are unique; i.e. two dynamic schedules cannot have the same name. If `set_schedule` is
396
+ passed the name of an existing schedule, that schedule is updated. E.g. if after setting the above schedule
397
+ we want the job to run every day instead of every hour from now on, we can do:
398
+
399
+ ```ruby
400
+ name = 'send_emails'
401
+ config = {}
402
+ config[:class] = 'SendEmail'
403
+ config[:args] = 'POC email subject'
404
+ config[:every] = '1d'
405
+ Resque.set_schedule(name, config)
406
+ ```
407
+
408
+ #### Time zones
409
+
410
+ Note that if you use the cron syntax, this will be interpreted as in the server time zone
411
+ rather than the `config.time_zone` specified in Rails.
412
+
413
+ You can explicitly specify the time zone that rufus-scheduler will use:
414
+
415
+ ```yaml
416
+ cron: "30 6 * * 1 Europe/Stockholm"
417
+ ```
418
+
419
+ Also note that `config.time_zone` in Rails allows for a shorthand (e.g. "Stockholm")
420
+ that rufus-scheduler does not accept. If you write code to set the scheduler time zone
421
+ from the `config.time_zone` value, make sure it's the right format, e.g. with:
422
+
423
+ ```ruby
424
+ ActiveSupport::TimeZone.find_tzinfo(Rails.configuration.time_zone).name
425
+ ```
426
+
427
+ A future version of resque-scheduler may do this for you.
428
+
429
+ #### Hooks
430
+
431
+ Similar to the `before_enqueue`- and `after_enqueue`-hooks provided in Resque
432
+ (>= 1.19.1), your jobs can specify one or more of the following hooks:
433
+
434
+ * `before_schedule`: Called with the job args before a job is placed on
435
+ the delayed queue. If the hook returns `false`, the job will not be placed on
436
+ the queue.
437
+ * `after_schedule`: Called with the job args after a job is placed on the
438
+ delayed queue. Any exception raised propagates up to the code with queued the
439
+ job.
440
+ * `before_delayed_enqueue`: Called with the job args after the job has been
441
+ removed from the delayed queue, but not yet put on a normal queue. It is
442
+ called before `before_enqueue`-hooks, and on the same job instance as the
443
+ `before_enqueue`-hooks will be invoked on. Return values are ignored.
444
+ * `on_enqueue_failure`: Called with the job args and the exception that was raised
445
+ while enqueueing a job to resque or external application fails. Return
446
+ values are ignored. For example:
447
+
448
+ ```ruby
449
+ Resque::Scheduler.failure_handler = ExceptionHandlerClass
450
+ ```
451
+
452
+ #### Support for resque-status (and other custom jobs)
453
+
454
+ Some Resque extensions like
455
+ [resque-status](http://github.com/quirkey/resque-status) use custom job
456
+ classes with a slightly different API signature. Resque-scheduler isn't
457
+ trying to support all existing and future custom job classes, instead it
458
+ supports a schedule flag so you can extend your custom class and make it
459
+ support scheduled job.
460
+
461
+ Let's pretend we have a `JobWithStatus` class called `FakeLeaderboard`
462
+
463
+ ```ruby
464
+ class FakeLeaderboard < Resque::JobWithStatus
465
+ def perform
466
+ # do something and keep track of the status
467
+ end
468
+ end
469
+ ```
470
+
471
+ And then a schedule:
472
+
473
+ ```yaml
474
+ create_fake_leaderboards:
475
+ cron: "30 6 * * 1"
476
+ queue: scoring
477
+ custom_job_class: "FakeLeaderboard"
478
+ args:
479
+ rails_env: demo
480
+ description: "This job will auto-create leaderboards for our online demo and the status will update as the worker makes progress"
481
+ ```
482
+
483
+ If your extension doesn't support scheduled job, you would need to extend the
484
+ custom job class to support the #scheduled method:
485
+
486
+ ```ruby
487
+ module Resque
488
+ class JobWithStatus
489
+ # Wrapper API to forward a Resque::Job creation API call into
490
+ # a JobWithStatus call.
491
+ def self.scheduled(queue, klass, *args)
492
+ create(*args)
493
+ end
494
+ end
495
+ end
496
+ ```
497
+
498
+ ### Redundancy and Fail-Over
499
+
500
+ *>= 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.*
501
+
502
+ You may want to have resque-scheduler running on multiple machines for
503
+ redundancy. Electing a master and failover is built in and default. Simply
504
+ run resque-scheduler on as many machine as you want pointing to the same
505
+ redis instance and schedule. The scheduler processes will use redis to
506
+ elect a master process and detect failover when the master dies. Precautions are
507
+ taken to prevent jobs from potentially being queued twice during failover even
508
+ when the clocks of the scheduler machines are slightly out of sync (or load affects
509
+ scheduled job firing time). If you want the gory details, look at Resque::Scheduler::Locking.
510
+
511
+ If the scheduler process(es) goes down for whatever reason, the delayed items
512
+ that should have fired during the outage will fire once the scheduler process
513
+ is started back up again (regardless of it being on a new machine). Missed
514
+ scheduled jobs, however, will not fire upon recovery of the scheduler process.
515
+ Think of scheduled (recurring) jobs as cron jobs - if you stop cron, it doesn't fire
516
+ missed jobs once it starts back up.
517
+
518
+ You might want to share a redis instance amongst multiple Rails applications with different
519
+ scheduler with different config yaml files. If this is the case, normally, only one will ever
520
+ run, leading to undesired behaviour. To allow different scheduler configs run at the same time
521
+ on one redis, you can either namespace your redis connections, or supply an environment variable
522
+ to split the shared lock key resque-scheduler uses thus:
523
+
524
+ ``` bash
525
+ RESQUE_SCHEDULER_MASTER_LOCK_PREFIX=MyApp: rake resque:scheduler
526
+ ```
527
+
528
+ ### resque-web Additions
529
+
530
+ Resque-scheduler also adds two tabs to the resque-web UI. One is for viewing
531
+ (and manually queueing) the schedule and one is for viewing pending jobs in
532
+ the delayed queue.
533
+
534
+ The Schedule tab:
535
+
536
+ ![The Schedule Tab](https://f.cloud.github.com/assets/45143/1178456/c99e5568-21b0-11e3-8c57-e1305d0ee8ef.png)
537
+
538
+ The Delayed tab:
539
+
540
+ ![The Delayed Tab](http://img.skitch.com/20100111-ne4fcqtc5emkcuwc5qtais2kwx.jpg)
541
+
542
+ #### How do I get the schedule tabs to show up???
543
+
544
+ To get these to show up you need to pass a file to `resque-web` to tell it to
545
+ include the `resque-scheduler` plugin and the resque-schedule server extension
546
+ to the resque-web sinatra app. Unless you're running redis on localhost, you
547
+ probably already have this file. It probably looks something like this:
548
+
549
+ ```ruby
550
+ require 'resque' # include resque so we can configure it
551
+ Resque.redis = "redis_server:6379" # tell Resque where redis lives
552
+ ```
553
+
554
+ Now, you want to add the following:
555
+
556
+ ```ruby
557
+ # This will make the tabs show up.
558
+ require 'resque-scheduler'
559
+ require 'resque/scheduler/server'
560
+ ```
561
+
562
+ That should make the scheduler tabs show up in `resque-web`.
563
+
564
+ You'll want to make sure you load the schedule in this file as well.
565
+ Something like this:
566
+
567
+ ```ruby
568
+ Resque.schedule = YAML.load_file(File.join(RAILS_ROOT, 'config/resque_schedule.yml')) # load the schedule
569
+ ```
570
+
571
+ Now make sure you're passing that file to resque-web like so:
572
+
573
+ resque-web ~/yourapp/config/resque_config.rb
574
+
575
+
576
+ ### Running in the background
577
+
578
+ (Only supported with ruby >= 1.9). There are scenarios where it's helpful for
579
+ the resque worker to run itself in the background (usually in combination with
580
+ PIDFILE). Use the BACKGROUND option so that rake will return as soon as the
581
+ worker is started.
582
+
583
+ $ PIDFILE=./resque-scheduler.pid BACKGROUND=yes \
584
+ rake resque:scheduler
585
+
586
+
587
+ ### Logging
588
+
589
+ There are several options to toggle the way scheduler logs its actions. They
590
+ are toggled by environment variables:
591
+
592
+ - `QUIET` will stop logging anything. Completely silent.
593
+ - `VERBOSE` opposite of 'QUIET'; will log even debug information
594
+ - `LOGFILE` specifies the file to write logs to. (default standard output)
595
+ - `LOGFORMAT` specifies either "text" or "json" output format
596
+ (default "text")
597
+
598
+ All of these variables are optional and will be given the following default
599
+ values:
600
+
601
+ ```ruby
602
+ Resque::Scheduler.configure do |c|
603
+ c.quiet = false
604
+ c.verbose = false
605
+ c.logfile = nil # meaning all messages go to $stdout
606
+ c.logformat = 'text'
607
+ end
608
+ ```
609
+
610
+ ### Polling frequency
611
+
612
+ You can pass a `RESQUE_SCHEDULER_INTERVAL` option which is an integer or
613
+ float representing the polling frequency. The default is 5 seconds, but
614
+ for a semi-active app you may want to use a smaller value.
615
+
616
+ $ RESQUE_SCHEDULER_INTERVAL=1 rake resque:scheduler
617
+
618
+ **NOTE** This value was previously `INTERVAL` but was renamed to
619
+ `RESQUE_SCHEDULER_INTERVAL` to avoid clashing with the interval Resque
620
+ uses for its jobs.
621
+
622
+ ### Plagiarism alert
623
+
624
+ This was intended to be an extension to resque and so resulted in a lot
625
+ of the code looking very similar to resque, particularly in resque-web
626
+ and the views. I wanted it to be similar enough that someone familiar
627
+ with resque could easily work on resque-scheduler.
628
+
629
+ ### Development
630
+
631
+ Working on resque-scheduler requires the following:
632
+
633
+ * A relatively modern Ruby interpreter
634
+ * bundler
635
+
636
+ The development setup looks like this, which is roughly the same thing
637
+ that happens on Travis CI and Appveyor:
638
+
639
+ ``` bash
640
+ # Install everything
641
+ bundle install
642
+
643
+ # Make sure tests are green before you change stuff
644
+ bundle exec rake
645
+ # Change stuff
646
+ # Repeat
647
+ ```
648
+
649
+ If you have [vagrant](http://www.vagrantup.com) installed, there is a
650
+ development box available that requires no plugins or external
651
+ provisioners:
652
+
653
+ ``` bash
654
+ vagrant up
655
+ ```
656
+
657
+ ### Deployment Notes
658
+
659
+ It is recommended that a production deployment of `resque-scheduler` be hosted
660
+ on a dedicated Redis database. While making and managing scheduled tasks,
661
+ `resque-scheduler` currently scans the entire Redis keyspace, which may cause
662
+ latency and stability issues if `resque-scheduler` is hosted on a Redis instance
663
+ storing a large number of keys (such as those written by a different system
664
+ hosted on the same Redis instance).
665
+
666
+ #### Compatibility Notes
667
+
668
+ Different versions of the `redis` and `rufus-scheduler` gems are needed
669
+ depending on your version of `resque-scheduler`. This is typically not a
670
+ problem with `resque-scheduler` itself, but when mixing dependencies with an
671
+ existing application.
672
+
673
+ This table explains the version requirements for redis gem
674
+
675
+ | resque-scheduler | redis gem |
676
+ |:-----------------|-----------:|
677
+ | `~> 2.0` | `>= 3.0.0` |
678
+ | `>= 0.0.1` | `~> 1.3` |
679
+
680
+ This table explains the version requirements for rufus-scheduler
681
+
682
+ | resque-scheduler | rufus-scheduler |
683
+ |:-----------------|----------------:|
684
+ | `~> 4.0` | `~> 3.0` |
685
+ | `< 4.0` | `~> 2.0` |
686
+
687
+
688
+ ### Contributing
689
+
690
+ See [CONTRIBUTING.md](CONTRIBUTING.md)
691
+
692
+ ### Authors
693
+
694
+ See [AUTHORS.md](AUTHORS.md)
695
+
696
+ ### License
697
+
698
+ See [LICENSE](LICENSE)