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.

@@ -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/defunkt/resque)
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
- | >= 2.0.0 | >= 2.2.0 |
14
- | >= 0.0.1 | >= 1.3 |
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
- Resque.enqueue_in(5.days, SendFollowupEmail) # run a job in 5 days
25
- # or
26
- Resque.enqueue_at(5.days.from_now, SomeJob) # run SomeJob at a specific time
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
- gem 'resque-scheduler', :require => 'resque_scheduler'
47
+ ```ruby
48
+ gem 'resque-scheduler', :require => 'resque_scheduler'
49
+ ```
42
50
 
43
51
  Adding the resque:scheduler rake task:
44
52
 
45
- require 'resque_scheduler/tasks'
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
- # Resque tasks
57
- require 'resque/tasks'
58
- require 'resque_scheduler/tasks'
59
-
60
- namespace :resque do
61
- task :setup do
62
- require 'resque'
63
- require 'resque_scheduler'
64
- require 'resque/scheduler'
65
-
66
- # you probably already have this somewhere
67
- Resque.redis = 'localhost:6379'
68
-
69
- # If you want to be able to dynamically change the schedule,
70
- # uncomment this line. A dynamic schedule can be updated via the
71
- # Resque::Scheduler.set_schedule (and remove_schedule) methods.
72
- # When dynamic is set to true, the scheduler process looks for
73
- # schedule changes and applies them on the fly.
74
- # Note: This feature is only available in >=2.0.0.
75
- #Resque::Scheduler.dynamic = true
76
-
77
- # The schedule doesn't need to be stored in a YAML, it just needs to
78
- # be a hash. YAML is usually the easiest.
79
- Resque.schedule = YAML.load_file('your_resque_schedule.yml')
80
-
81
- # If your schedule already has +queue+ set for each job, you don't
82
- # need to require your jobs. This can be an advantage since it's
83
- # less code that resque-scheduler needs to know about. But in a small
84
- # project, it's usually easier to just include you job classes here.
85
- # So, someting like this:
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
- NOTE: You DO NOT want to run >1 instance of the scheduler. Doing so will
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
- If the scheduler process goes down for whatever reason, the delayed items
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
- Resque.enqueue_in(5.days, SendFollowUpEmail, :user_id => current_user.id)
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
- # after you've enqueued a job like:
151
- Resque.enqueue_at(5.days.from_now, SendFollowUpEmail, :user_id => current_user.id)
152
- # remove the job with exactly the same parameters:
153
- Resque.remove_delayed(SendFollowUpEmail, :user_id => current_user.id)
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
- CancelAbandonedOrders:
166
- cron: "*/5 * * * *"
167
-
168
- queue_documents_for_indexing:
169
- cron: "0 0 * * *"
170
- # you can use rufus-scheduler "every" syntax in place of cron if you prefer
171
- # every: 1hr
172
- # By default the job name (hash key) will be taken as worker class name.
173
- # If you want to have a different job name and class name, provide the 'class' option
174
- class: QueueDocuments
175
- queue: high
176
- args:
177
- description: "This job queues all content for indexing in solr"
178
-
179
- clear_leaderboards_contributors:
180
- cron: "30 6 * * 1"
181
- class: ClearLeaderboards
182
- queue: low
183
- args: contributors
184
- description: "This job resets the weekly leaderboard for contributions"
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
- clear_leaderboards_moderator:
195
- every: ["30s", :first_in => '120s']
196
- class: CheckDaemon
197
- queue: daemons
198
- description: "This job will check Daemon every 30 seconds after 120 seconds after start"
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
- cron: "30 6 * * 1 Europe/Stockholm"
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
- ActiveSupport::TimeZone.find_tzinfo(Rails.configuration.time_zone).name
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
- class FakeLeaderboard < Resque::JobWithStatus
255
- def perform
256
- # do something and keep track of the status
257
- end
258
- end
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
- create_fake_leaderboards:
263
- cron: "30 6 * * 1"
264
- queue: scoring
265
- custom_job_class: FakeLeaderboard
266
- args:
267
- rails_env: demo
268
- description: "This job will auto-create leaderboards for our online demo and the status will update as the worker makes progress"
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
- module Resque
274
- class JobWithStatus
275
- # Wrapper API to forward a Resque::Job creation API call into
276
- # a JobWithStatus call.
277
- def self.scheduled(queue, klass, *args)
278
- create(*args)
279
- end
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](http://img.skitch.com/20100111-km2f5gmtpbq23enpujbruj6mgk.png)
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
- require 'resque' # include resque so we can configure it
307
- Resque.redis = "redis_server:6379" # tell Resque where redis lives
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
- # This will make the tabs show up.
312
- require 'resque_scheduler'
313
- require 'resque_scheduler/server'
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
- Resque.schedule = YAML.load_file(File.join(RAILS_ROOT, 'config/resque_schedule.yml')) # load the schedule
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
- For bugs or suggestions, please just open an issue in github.
451
+ See [CONTRIBUTING.md](CONTRIBUTING.md)
452
+
453
+ ### Authors
454
+
455
+ See [AUTHORS.md](AUTHORS.md)
456
+
457
+ ### License
353
458
 
354
- Patches are always welcome.
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
- # Tests
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
- # Documentation Tasks
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 = "README.markdown"
23
- rd.rdoc_files.include("README.markdown", "lib/**/*.rb")
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
@@ -0,0 +1,3 @@
1
+ %w[base basic resilient].each do |file|
2
+ require "resque/scheduler/lock/#{file}"
3
+ end