resque-scheduler 2.0.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|
+
[](https://gemnasium.com/resque/resque-scheduler)
|
5
|
+
[](http://badge.fury.io/rb/resque-scheduler)
|
6
|
+
[](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
|
-

|
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
|