resque-scheduler 2.0.0 → 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of resque-scheduler might be problematic. Click here for more details.
- checksums.yaml +15 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +129 -0
- data/.travis.yml +18 -0
- data/AUTHORS.md +63 -0
- data/CONTRIBUTING.md +6 -0
- data/Gemfile +3 -7
- data/HISTORY.md +65 -6
- data/LICENSE +3 -1
- data/{README.markdown → README.md} +218 -113
- data/Rakefile +22 -9
- data/lib/resque/scheduler/lock/base.rb +52 -0
- data/lib/resque/scheduler/lock/basic.rb +28 -0
- data/lib/resque/scheduler/lock/resilient.rb +69 -0
- data/lib/resque/scheduler/lock.rb +3 -0
- data/lib/resque/scheduler.rb +56 -19
- data/lib/resque/scheduler_locking.rb +89 -0
- data/lib/resque_scheduler/logger_builder.rb +51 -0
- data/lib/resque_scheduler/server/views/delayed.erb +2 -1
- data/lib/resque_scheduler/server/views/requeue-params.erb +23 -0
- data/lib/resque_scheduler/server/views/scheduler.erb +4 -3
- data/lib/resque_scheduler/server.rb +25 -3
- data/lib/resque_scheduler/tasks.rb +9 -3
- data/lib/resque_scheduler/util.rb +34 -0
- data/lib/resque_scheduler/version.rb +3 -1
- data/lib/resque_scheduler.rb +89 -38
- data/resque-scheduler.gemspec +26 -20
- data/script/migrate_to_timestamps_set.rb +14 -0
- data/test/delayed_queue_test.rb +67 -16
- data/test/redis-test.conf +0 -7
- data/test/resque-web_test.rb +105 -3
- data/test/scheduler_args_test.rb +1 -1
- data/test/scheduler_locking_test.rb +180 -0
- data/test/scheduler_setup_test.rb +59 -0
- data/test/scheduler_test.rb +21 -12
- data/test/support/redis_instance.rb +129 -0
- data/test/test_helper.rb +26 -14
- metadata +105 -32
@@ -1,17 +1,21 @@
|
|
1
1
|
resque-scheduler
|
2
2
|
================
|
3
3
|
|
4
|
+
[![Dependency Status](https://gemnasium.com/resque/resque-scheduler.png)](https://gemnasium.com/resque/resque-scheduler)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/resque-scheduler.png)](http://badge.fury.io/rb/resque-scheduler)
|
6
|
+
[![Build Status](https://travis-ci.org/resque/resque-scheduler.png?branch=master)](https://travis-ci.org/resque/resque-scheduler)
|
7
|
+
|
4
8
|
### Description
|
5
9
|
|
6
|
-
Resque-scheduler is an extension to [Resque](http://github.com/
|
10
|
+
Resque-scheduler is an extension to [Resque](http://github.com/resque/resque)
|
7
11
|
that adds support for queueing items in the future.
|
8
12
|
|
9
13
|
This table explains the version requirements for redis
|
10
14
|
|
11
15
|
| resque-scheduler version | required redis version|
|
12
16
|
|:-------------------------|----------------------:|
|
13
|
-
|
|
14
|
-
| >= 0.0.1 |
|
17
|
+
| ~> 2.0 | >= 3.0.0 |
|
18
|
+
| >= 0.0.1 | ~> 1.3 |
|
15
19
|
|
16
20
|
|
17
21
|
Job scheduling is supported in two different way: Recurring (scheduled) and
|
@@ -21,9 +25,11 @@ Scheduled jobs are like cron jobs, recurring on a regular basis. Delayed
|
|
21
25
|
jobs are resque jobs that you want to run at some point in the future.
|
22
26
|
The syntax is pretty explanatory:
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
```ruby
|
29
|
+
Resque.enqueue_in(5.days, SendFollowupEmail) # run a job in 5 days
|
30
|
+
# or
|
31
|
+
Resque.enqueue_at(5.days.from_now, SomeJob) # run SomeJob at a specific time
|
32
|
+
```
|
27
33
|
|
28
34
|
### Documentation
|
29
35
|
|
@@ -38,11 +44,15 @@ To install:
|
|
38
44
|
|
39
45
|
If you use a Gemfile, you may want to specify the `:require` explicitly:
|
40
46
|
|
41
|
-
|
47
|
+
```ruby
|
48
|
+
gem 'resque-scheduler', :require => 'resque_scheduler'
|
49
|
+
```
|
42
50
|
|
43
51
|
Adding the resque:scheduler rake task:
|
44
52
|
|
45
|
-
|
53
|
+
```ruby
|
54
|
+
require 'resque_scheduler/tasks'
|
55
|
+
```
|
46
56
|
|
47
57
|
There are three things `resque-scheduler` needs to know about in order to do
|
48
58
|
it's jobs: the schedule, where redis lives, and which queues to use. The
|
@@ -52,40 +62,41 @@ probably already have this task, lets just put our configuration there.
|
|
52
62
|
`resque-scheduler` pretty much needs to know everything `resque` needs
|
53
63
|
to know.
|
54
64
|
|
65
|
+
```ruby
|
66
|
+
# Resque tasks
|
67
|
+
require 'resque/tasks'
|
68
|
+
require 'resque_scheduler/tasks'
|
55
69
|
|
56
|
-
|
57
|
-
|
58
|
-
require '
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
require 'jobs'
|
87
|
-
end
|
88
|
-
end
|
70
|
+
namespace :resque do
|
71
|
+
task :setup do
|
72
|
+
require 'resque'
|
73
|
+
require 'resque_scheduler'
|
74
|
+
require 'resque/scheduler'
|
75
|
+
|
76
|
+
# you probably already have this somewhere
|
77
|
+
Resque.redis = 'localhost:6379'
|
78
|
+
|
79
|
+
# If you want to be able to dynamically change the schedule,
|
80
|
+
# uncomment this line. A dynamic schedule can be updated via the
|
81
|
+
# Resque::Scheduler.set_schedule (and remove_schedule) methods.
|
82
|
+
# When dynamic is set to true, the scheduler process looks for
|
83
|
+
# schedule changes and applies them on the fly.
|
84
|
+
# Note: This feature is only available in >=2.0.0.
|
85
|
+
#Resque::Scheduler.dynamic = true
|
86
|
+
|
87
|
+
# The schedule doesn't need to be stored in a YAML, it just needs to
|
88
|
+
# be a hash. YAML is usually the easiest.
|
89
|
+
Resque.schedule = YAML.load_file('your_resque_schedule.yml')
|
90
|
+
|
91
|
+
# If your schedule already has +queue+ set for each job, you don't
|
92
|
+
# need to require your jobs. This can be an advantage since it's
|
93
|
+
# less code that resque-scheduler needs to know about. But in a small
|
94
|
+
# project, it's usually easier to just include you job classes here.
|
95
|
+
# So, something like this:
|
96
|
+
require 'jobs'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
89
100
|
|
90
101
|
The scheduler process is just a rake task which is responsible for both
|
91
102
|
queueing items from the schedule and polling the delayed queue for items
|
@@ -99,16 +110,17 @@ any nonempty value, they will take effect. `VERBOSE` simply dumps more output
|
|
99
110
|
to stdout. `MUTE` does the opposite and silences all output. `MUTE`
|
100
111
|
supersedes `VERBOSE`.
|
101
112
|
|
102
|
-
|
103
|
-
result in the same job being queued more than once. You only need one
|
104
|
-
instance of the scheduler running per resque instance (regardless of number
|
105
|
-
of machines).
|
113
|
+
### Resque Pool integration
|
106
114
|
|
107
|
-
|
108
|
-
that should have fired during the outage will fire once the scheduler process
|
109
|
-
is started back up again (regardless of it being on a new machine). Missed
|
110
|
-
scheduled jobs, however, will not fire upon recovery of the scheduler process.
|
115
|
+
For normal work with [resque-pool](https://github.com/nevans/resque-pool) gem add next lines in lib/rake/resque.rake
|
111
116
|
|
117
|
+
```ruby
|
118
|
+
task 'resque:pool:setup' do
|
119
|
+
Resque::Pool.after_prefork do |job|
|
120
|
+
Resque.redis.client.reconnect
|
121
|
+
end
|
122
|
+
end
|
123
|
+
```
|
112
124
|
|
113
125
|
|
114
126
|
### Delayed jobs
|
@@ -116,7 +128,9 @@ scheduled jobs, however, will not fire upon recovery of the scheduler process.
|
|
116
128
|
Delayed jobs are one-off jobs that you want to be put into a queue at some point
|
117
129
|
in the future. The classic example is sending email:
|
118
130
|
|
119
|
-
|
131
|
+
```ruby
|
132
|
+
Resque.enqueue_in(5.days, SendFollowUpEmail, :user_id => current_user.id)
|
133
|
+
```
|
120
134
|
|
121
135
|
This will store the job for 5 days in the resque delayed queue at which time
|
122
136
|
the scheduler process will pull it from the delayed queue and put it in the
|
@@ -147,10 +161,12 @@ disclosure is always best.
|
|
147
161
|
|
148
162
|
If you have the need to cancel a delayed job, you can do like so:
|
149
163
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
164
|
+
```ruby
|
165
|
+
# after you've enqueued a job like:
|
166
|
+
Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :user_id => current_user.id)
|
167
|
+
# remove the job with exactly the same parameters:
|
168
|
+
Resque.remove_delayed(SendFollowUpEmail, :user_id => current_user.id)
|
169
|
+
```
|
154
170
|
|
155
171
|
### Scheduled Jobs (Recurring Jobs)
|
156
172
|
|
@@ -162,26 +178,28 @@ The schedule is a list of Resque worker classes with arguments and a
|
|
162
178
|
schedule frequency (in crontab syntax). The schedule is just a hash, but
|
163
179
|
is most likely stored in a YAML like so:
|
164
180
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
181
|
+
```yaml
|
182
|
+
CancelAbandonedOrders:
|
183
|
+
cron: "*/5 * * * *"
|
184
|
+
|
185
|
+
queue_documents_for_indexing:
|
186
|
+
cron: "0 0 * * *"
|
187
|
+
# you can use rufus-scheduler "every" syntax in place of cron if you prefer
|
188
|
+
# every: 1hr
|
189
|
+
# By default the job name (hash key) will be taken as worker class name.
|
190
|
+
# If you want to have a different job name and class name, provide the 'class' option
|
191
|
+
class: QueueDocuments
|
192
|
+
queue: high
|
193
|
+
args:
|
194
|
+
description: "This job queues all content for indexing in solr"
|
195
|
+
|
196
|
+
clear_leaderboards_contributors:
|
197
|
+
cron: "30 6 * * 1"
|
198
|
+
class: ClearLeaderboards
|
199
|
+
queue: low
|
200
|
+
args: contributors
|
201
|
+
description: "This job resets the weekly leaderboard for contributions"
|
202
|
+
```
|
185
203
|
|
186
204
|
The queue value is optional, but if left unspecified resque-scheduler will
|
187
205
|
attempt to get the queue from the job class, which means it needs to be
|
@@ -191,16 +209,24 @@ need to either set the queue in the schedule or require your jobs in your
|
|
191
209
|
|
192
210
|
You can provide options to "every" or "cron" via Array:
|
193
211
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
212
|
+
```yaml
|
213
|
+
clear_leaderboards_moderator:
|
214
|
+
every:
|
215
|
+
- "30s"
|
216
|
+
- :first_in: "120s"
|
217
|
+
class: CheckDaemon
|
218
|
+
queue: daemons
|
219
|
+
description: "This job will check Daemon every 30 seconds after 120 seconds after start"
|
220
|
+
```
|
221
|
+
|
222
|
+
IMPORTANT: Rufus `every` syntax will calculate jobs scheduling time starting from the moment of deploy,
|
223
|
+
resulting in resetting schedule time on every deploy, so it's probably a good idea to use it only for
|
224
|
+
frequent jobs (like every 10-30 minutes), otherwise - when you use something like `every 20h` and deploy once-twice per day -
|
225
|
+
it will schedule the job for 20 hours from deploy, resulting in a job to never be run.
|
200
226
|
|
201
227
|
NOTE: Six parameter cron's are also supported (as they supported by
|
202
228
|
rufus-scheduler which powers the resque-scheduler process). This allows you
|
203
|
-
to schedule jobs per second (ie: "30 * * * * *" would fire a job every 30
|
229
|
+
to schedule jobs per second (ie: `"30 * * * * *"` would fire a job every 30
|
204
230
|
seconds past the minute).
|
205
231
|
|
206
232
|
A big shout out to [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler)
|
@@ -214,13 +240,17 @@ rather than the `config.time_zone` specified in Rails.
|
|
214
240
|
|
215
241
|
You can explicitly specify the time zone that rufus-scheduler will use:
|
216
242
|
|
217
|
-
|
243
|
+
```yaml
|
244
|
+
cron: "30 6 * * 1 Europe/Stockholm"
|
245
|
+
```
|
218
246
|
|
219
247
|
Also note that `config.time_zone` in Rails allows for a shorthand (e.g. "Stockholm")
|
220
248
|
that rufus-scheduler does not accept. If you write code to set the scheduler time zone
|
221
249
|
from the `config.time_zone` value, make sure it's the right format, e.g. with:
|
222
250
|
|
223
|
-
|
251
|
+
```ruby
|
252
|
+
ActiveSupport::TimeZone.find_tzinfo(Rails.configuration.time_zone).name
|
253
|
+
```
|
224
254
|
|
225
255
|
A future version of resque-scheduler may do this for you.
|
226
256
|
|
@@ -249,38 +279,70 @@ trying to support all existing and future custom job classes, instead it
|
|
249
279
|
supports a schedule flag so you can extend your custom class and make it
|
250
280
|
support scheduled job.
|
251
281
|
|
252
|
-
Let's pretend we have a JobWithStatus class called FakeLeaderboard
|
282
|
+
Let's pretend we have a `JobWithStatus` class called `FakeLeaderboard`
|
253
283
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
284
|
+
```ruby
|
285
|
+
class FakeLeaderboard < Resque::JobWithStatus
|
286
|
+
def perform
|
287
|
+
# do something and keep track of the status
|
288
|
+
end
|
289
|
+
end
|
290
|
+
```
|
259
291
|
|
260
292
|
And then a schedule:
|
261
293
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
294
|
+
```yaml
|
295
|
+
create_fake_leaderboards:
|
296
|
+
cron: "30 6 * * 1"
|
297
|
+
queue: scoring
|
298
|
+
custom_job_class: FakeLeaderboard
|
299
|
+
args:
|
300
|
+
rails_env: demo
|
301
|
+
description: "This job will auto-create leaderboards for our online demo and the status will update as the worker makes progress"
|
302
|
+
```
|
269
303
|
|
270
304
|
If your extension doesn't support scheduled job, you would need to extend the
|
271
305
|
custom job class to support the #scheduled method:
|
272
306
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
end
|
307
|
+
```ruby
|
308
|
+
module Resque
|
309
|
+
class JobWithStatus
|
310
|
+
# Wrapper API to forward a Resque::Job creation API call into
|
311
|
+
# a JobWithStatus call.
|
312
|
+
def self.scheduled(queue, klass, *args)
|
313
|
+
create(*args)
|
281
314
|
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
### Redundancy and Fail-Over
|
320
|
+
|
321
|
+
*>= 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.*
|
322
|
+
|
323
|
+
You may want to have resque-scheduler running on multiple machines for
|
324
|
+
redudancy. Electing a master and failover is built in and default. Simply
|
325
|
+
run resque-scheduler on as many machine as you want pointing to the same
|
326
|
+
redis instance and schedule. The scheduler processes will use redis to
|
327
|
+
elect a master process and detect failover when the master dies. Precautions are
|
328
|
+
taken to prevent jobs from potentially being queued twice during failover even
|
329
|
+
when the clocks of the scheduler machines are slightly out of sync (or load affects
|
330
|
+
scheduled job firing time). If you want the gory details, look at Resque::SchedulerLocking.
|
331
|
+
|
332
|
+
If the scheduler process(es) goes down for whatever reason, the delayed items
|
333
|
+
that should have fired during the outage will fire once the scheduler process
|
334
|
+
is started back up again (regardless of it being on a new machine). Missed
|
335
|
+
scheduled jobs, however, will not fire upon recovery of the scheduler process.
|
336
|
+
Think of scheduled (recurring) jobs as cron jobs - if you stop cron, it doesn't fire
|
337
|
+
missed jobs once it starts back up.
|
282
338
|
|
339
|
+
You might want to share a redis instance amongst multiple Rails applications with different
|
340
|
+
scheduler with different config yaml files. If this is the case, normally, only one will ever
|
341
|
+
run, leading to undesired behaviour. To allow different scheduler configs run at the same time
|
342
|
+
on one redis, you can either namespace your redis connections, or supply an environment variable
|
343
|
+
to split the shared lock key resque-scheduler uses thus:
|
283
344
|
|
345
|
+
RESQUE_SCHEDULER_MASTER_LOCK_PREFIX=MyApp: rake resque:scheduler
|
284
346
|
|
285
347
|
### resque-web Additions
|
286
348
|
|
@@ -290,7 +352,7 @@ the delayed queue.
|
|
290
352
|
|
291
353
|
The Schedule tab:
|
292
354
|
|
293
|
-
![The Schedule Tab](
|
355
|
+
![The Schedule Tab](https://f.cloud.github.com/assets/45143/1178456/c99e5568-21b0-11e3-8c57-e1305d0ee8ef.png)
|
294
356
|
|
295
357
|
The Delayed tab:
|
296
358
|
|
@@ -303,14 +365,18 @@ include the `resque-scheduler` plugin and the resque-schedule server extension
|
|
303
365
|
to the resque-web sinatra app. Unless you're running redis on localhost, you
|
304
366
|
probably already have this file. It probably looks something like this:
|
305
367
|
|
306
|
-
|
307
|
-
|
368
|
+
```ruby
|
369
|
+
require 'resque' # include resque so we can configure it
|
370
|
+
Resque.redis = "redis_server:6379" # tell Resque where redis lives
|
371
|
+
```
|
308
372
|
|
309
373
|
Now, you want to add the following:
|
310
374
|
|
311
|
-
|
312
|
-
|
313
|
-
|
375
|
+
```ruby
|
376
|
+
# This will make the tabs show up.
|
377
|
+
require 'resque_scheduler'
|
378
|
+
require 'resque_scheduler/server'
|
379
|
+
```
|
314
380
|
|
315
381
|
That should make the scheduler tabs show up in `resque-web`.
|
316
382
|
|
@@ -322,7 +388,9 @@ process aware of the schedule because it reads it from redis. But prior to
|
|
322
388
|
2.0, you'll want to make sure you load the schedule in this file as well.
|
323
389
|
Something like this:
|
324
390
|
|
325
|
-
|
391
|
+
```ruby
|
392
|
+
Resque.schedule = YAML.load_file(File.join(RAILS_ROOT, 'config/resque_schedule.yml')) # load the schedule
|
393
|
+
```
|
326
394
|
|
327
395
|
Now make sure you're passing that file to resque-web like so:
|
328
396
|
|
@@ -339,6 +407,37 @@ worker is started.
|
|
339
407
|
$ PIDFILE=./resque-scheduler.pid BACKGROUND=yes \
|
340
408
|
rake resque:scheduler
|
341
409
|
|
410
|
+
|
411
|
+
### Logging
|
412
|
+
|
413
|
+
There are several options to toggle the way scheduler logs its actions. They
|
414
|
+
are toggled by environment variables:
|
415
|
+
|
416
|
+
- `MUTE` will stop logging anything. Completely silent;
|
417
|
+
- `VERBOSE` opposite to 'mute' will log even debug information;
|
418
|
+
- `LOGFILE` specifies the file to write logs to. Default is standard output.
|
419
|
+
|
420
|
+
All those variables are non-mandatory and default values are
|
421
|
+
|
422
|
+
```ruby
|
423
|
+
Resque::Scheduler.mute = false
|
424
|
+
Resque::Scheduler.verbose = false
|
425
|
+
Resque::Scheduler.logfile = nil # that means, all messages go to STDOUT
|
426
|
+
```
|
427
|
+
|
428
|
+
|
429
|
+
### Polling frequency
|
430
|
+
|
431
|
+
You can pass a `RESQUE_SCHEDULER_INTERVAL` option which is an integer or float
|
432
|
+
representing the polling frequency. The default is 5 seconds, but for a
|
433
|
+
semi-active app you may want to use a smaller value.
|
434
|
+
|
435
|
+
$ RESQUE_SCHEDULER_INTERVAL=1 rake resque:scheduler
|
436
|
+
|
437
|
+
**NOTE** This value was previously `INTERVAL` but was renamed to
|
438
|
+
`RESQUE_SCHEDULER_INTERVAL` to avoid clashing with the interval Resque
|
439
|
+
uses for its jobs.
|
440
|
+
|
342
441
|
### Plagiarism alert
|
343
442
|
|
344
443
|
This was intended to be an extension to resque and so resulted in a lot of the
|
@@ -349,6 +448,12 @@ work on resque-scheduler.
|
|
349
448
|
|
350
449
|
### Contributing
|
351
450
|
|
352
|
-
|
451
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
452
|
+
|
453
|
+
### Authors
|
454
|
+
|
455
|
+
See [AUTHORS.md](AUTHORS.md)
|
456
|
+
|
457
|
+
### License
|
353
458
|
|
354
|
-
|
459
|
+
See [LICENSE](LICENSE)
|
data/Rakefile
CHANGED
@@ -1,28 +1,41 @@
|
|
1
|
-
require 'bundler'
|
2
|
-
|
3
|
-
Bundler::GemHelper.install_tasks
|
1
|
+
require 'bundler/gem_tasks'
|
4
2
|
|
5
3
|
$LOAD_PATH.unshift 'lib'
|
6
4
|
|
7
5
|
task :default => :test
|
8
6
|
|
9
|
-
|
10
|
-
desc "Run tests"
|
7
|
+
desc 'Run tests'
|
11
8
|
task :test do
|
9
|
+
if RUBY_VERSION =~ /^1\.8/
|
10
|
+
unless ENV['SEED']
|
11
|
+
srand
|
12
|
+
ENV['SEED'] = (srand % 0xFFFF).to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
$stdout.puts "Running with SEED=#{ENV['SEED']}"
|
16
|
+
srand Integer(ENV['SEED'])
|
17
|
+
elsif ENV['SEED']
|
18
|
+
ARGV += %W(--seed #{ENV['SEED']})
|
19
|
+
end
|
12
20
|
Dir['test/*_test.rb'].each do |f|
|
13
21
|
require File.expand_path(f)
|
14
22
|
end
|
15
23
|
end
|
16
24
|
|
17
|
-
|
25
|
+
desc 'Run rubocop'
|
26
|
+
task :rubocop do
|
27
|
+
unless RUBY_VERSION < '1.9'
|
28
|
+
sh('rubocop --format simple') { |ok, _| ok || abort }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
18
32
|
begin
|
19
33
|
require 'rdoc/task'
|
20
34
|
|
21
35
|
Rake::RDocTask.new do |rd|
|
22
|
-
rd.main =
|
23
|
-
rd.rdoc_files.include(
|
36
|
+
rd.main = 'README.md'
|
37
|
+
rd.rdoc_files.include('README.md', 'lib/**/*.rb')
|
24
38
|
rd.rdoc_dir = 'doc'
|
25
39
|
end
|
26
40
|
rescue LoadError
|
27
41
|
end
|
28
|
-
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Resque
|
2
|
+
class Scheduler
|
3
|
+
module Lock
|
4
|
+
class Base
|
5
|
+
attr_reader :key
|
6
|
+
attr_accessor :timeout
|
7
|
+
|
8
|
+
def initialize(key, options = {})
|
9
|
+
@key = key
|
10
|
+
|
11
|
+
# 3 minute default timeout
|
12
|
+
@timeout = options[:timeout] || 60 * 3
|
13
|
+
end
|
14
|
+
|
15
|
+
# Attempts to acquire the lock. Returns true if successfully acquired.
|
16
|
+
def acquire!
|
17
|
+
fail NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def value
|
21
|
+
@value ||= [hostname, process_id].join(':')
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns true if you currently hold the lock.
|
25
|
+
def locked?
|
26
|
+
fail NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Releases the lock.
|
30
|
+
def release!
|
31
|
+
Resque.redis.del(key) == 1
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Extends the lock by `timeout` seconds.
|
37
|
+
def extend_lock!
|
38
|
+
Resque.redis.expire(key, timeout)
|
39
|
+
end
|
40
|
+
|
41
|
+
def hostname
|
42
|
+
local_hostname = Socket.gethostname
|
43
|
+
Socket.gethostbyname(local_hostname).first rescue local_hostname
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_id
|
47
|
+
Process.pid
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'resque/scheduler/lock/base'
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
class Scheduler
|
5
|
+
module Lock
|
6
|
+
class Basic < Base
|
7
|
+
def acquire!
|
8
|
+
if Resque.redis.setnx(key, value)
|
9
|
+
extend_lock!
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def locked?
|
15
|
+
if Resque.redis.get(key) == value
|
16
|
+
extend_lock!
|
17
|
+
|
18
|
+
if Resque.redis.get(key) == value
|
19
|
+
return true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'resque/scheduler/lock/base'
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
class Scheduler
|
5
|
+
module Lock
|
6
|
+
class Resilient < Base
|
7
|
+
def acquire!
|
8
|
+
Resque.redis.evalsha(
|
9
|
+
acquire_sha,
|
10
|
+
:keys => [key],
|
11
|
+
:argv => [value]
|
12
|
+
).to_i == 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def locked?
|
16
|
+
Resque.redis.evalsha(
|
17
|
+
locked_sha,
|
18
|
+
:keys => [key],
|
19
|
+
:argv => [value]
|
20
|
+
).to_i == 1
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def locked_sha(refresh = false)
|
26
|
+
@locked_sha = nil if refresh
|
27
|
+
|
28
|
+
@locked_sha ||= begin
|
29
|
+
Resque.redis.script(
|
30
|
+
:load,
|
31
|
+
<<-EOF
|
32
|
+
if redis.call('GET', KEYS[1]) == ARGV[1]
|
33
|
+
then
|
34
|
+
redis.call('EXPIRE', KEYS[1], #{timeout})
|
35
|
+
|
36
|
+
if redis.call('GET', KEYS[1]) == ARGV[1]
|
37
|
+
then
|
38
|
+
return 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
return 0
|
43
|
+
EOF
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def acquire_sha(refresh = false)
|
49
|
+
@acquire_sha = nil if refresh
|
50
|
+
|
51
|
+
@acquire_sha ||= begin
|
52
|
+
Resque.redis.script(
|
53
|
+
:load,
|
54
|
+
<<-EOF
|
55
|
+
if redis.call('SETNX', KEYS[1], ARGV[1]) == 1
|
56
|
+
then
|
57
|
+
redis.call('EXPIRE', KEYS[1], #{timeout})
|
58
|
+
return 1
|
59
|
+
else
|
60
|
+
return 0
|
61
|
+
end
|
62
|
+
EOF
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|