resque-unique_at_runtime 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5dac074b3279cfd0d0e3eaf495ead3828b3eaf73
4
+ data.tar.gz: 2e87da59e0cc4c4af1843659e19638d1365d90ab
5
+ SHA512:
6
+ metadata.gz: 1b6c50a917f58b0049d79264ae28f1258514d90b6e1ea23d8435f521491b1c2ce48803aad5423747b48ed561c936ad601b5b60e1a11c34bc3d02e3512b03c5e5
7
+ data.tar.gz: 863df5e7e568db23ab2509c1b0b9e3ddf36b2d8b555239cd462508923b7277e8a43e90563a9fe97718e2f7d5a345978c9aa5203fd9d72fb06a71d7f58e4e809b
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in resque-unique_at_runtime.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jonathan R. Wallace
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.
@@ -0,0 +1,191 @@
1
+ # Resque::Plugins::UniqueAtRuntime
2
+
3
+ [![Build Status](https://travis-ci.org/pboling/resque-unique_at_runtime.png)](https://travis-ci.org/pboling/resque-lonely\_job)
4
+
5
+ A [semanticaly versioned](http://semver.org/)
6
+ [Resque](https://github.com/resque/resque) plugin which ensures for a given
7
+ queue, that only one worker is working on a job at any given time.
8
+
9
+ Resque::Plugins::UniqueAtRuntime differs from [resque-lonely_job](https://github.com/wallace/resque-lonely_job) in that it is compatible with, and can be used at the same time as, [resque-solo](https://github.com/neighborland/resque_solo).
10
+
11
+ Resque::Plugins::UniqueAtRuntime differs from [resque_solo](https://github.com/neighborland/resque_solo) in that `resque-solo` offers **queue-time** uniqueness, while `resque-unique_at_runtime` offers **runtime** uniqueness. The same difference applies to other queue-time uniqueness gems: [resque-queue-lock](https://github.com/mashion/resque-queue-lock), [resque-lock](https://github.com/defunkt/resque-lock).
12
+
13
+ Runtime uniqueness without queue-time uniqueness means the same job may be queued multiple times but you're guaranteed that first job queued will run to completion before subsequent jobs are run.
14
+
15
+ However, you can use both runtime and queue-time uniqueness together in the same project.
16
+
17
+ To use `resque-solo` and `resque-unique_at_runtime` together, with fine control of per job configuration of uniqueness at runtime and queue-time, it is recommended to use [resque-unique_by_arity](https://github.com/pboling/resque-unique_by_arity).
18
+
19
+ NOTE: There is a *strong* possibility that subsequent jobs are re-ordered due to
20
+ the implementation of
21
+ [reenqueue](https://github.com/pboling/resque-unique_at_runtime/blob/master/lib/resque-unique_at_runtime.rb#L35).
22
+ (See Example #2 for an alternative approach that attempts to preserve job
23
+ ordering but introduces the possibility of starvation.)
24
+
25
+ Therefore it is recommended that the payload for jobs be stored in a separate
26
+ redis list distinct from the Resque queue (see Example #3).
27
+
28
+ ## Requirements
29
+
30
+ Requires a version of MRI Ruby >= 1.9.3.
31
+
32
+ ## Installation
33
+
34
+ Add this line to your application's Gemfile:
35
+
36
+ gem 'resque-unique_at_runtime', '~> 1.0.0'
37
+
38
+ And then execute:
39
+
40
+ $ bundle
41
+
42
+ Or install it yourself as:
43
+
44
+ $ gem install resque-unique_at_runtime
45
+
46
+ ## Usage
47
+
48
+ #### Example #1 -- One job running per queue
49
+
50
+ require 'resque-unique_at_runtime'
51
+
52
+ class StrictlySerialJob
53
+ extend Resque::Plugins::UniqueAtRuntime
54
+
55
+ @queue = :serial_work
56
+
57
+ def self.perform
58
+ # only one at a time in this block, no parallelism allowed for this
59
+ # particular queue
60
+ end
61
+ end
62
+
63
+ #### Example #2 -- One job running per user-defined attribute
64
+
65
+ Let's say you want the serial constraint to apply at a more granular
66
+ level. Instead of applying at the queue level, you can overwrite the .redis\_key
67
+ method.
68
+
69
+ require 'resque-unique_at_runtime'
70
+
71
+ class StrictlySerialJob
72
+ extend Resque::Plugins::UniqueAtRuntime
73
+
74
+ @queue = :serial_work
75
+
76
+ # Returns a string that will be used as the redis key
77
+ # NOTE: it is recommended to prefix your string with the 'unique_at_runtime:' to
78
+ # namespace your key!
79
+ def self.unique_at_runtime_redis_key(account_id, *args)
80
+ "unique_at_runtime:strictly_serial_job:#{account_id}"
81
+ end
82
+
83
+ # Overwrite reenqueue to lpush instead of default rpush. This attempts to
84
+ # preserve job ordering but job order is *NOT* guaranteed and also not
85
+ # likely. See the comment on SHA: e9912fb2 for why.
86
+ def self.reenqueue(*args)
87
+ Resque.redis.lpush("queue:#{Resque.queue_from_class(self)}", Resque.encode(class: self, args: args))
88
+ end
89
+
90
+ def self.perform(account_id, *args)
91
+ # only one at a time in this block, no parallelism allowed for this
92
+ # particular unique_at_runtime_redis_key
93
+ end
94
+ end
95
+
96
+ *NOTE*: Without careful consideration of your problem domain, worker starvation
97
+ and/or unfairness is possible for jobs in this example. Imagine a scenario
98
+ where you have three jobs in the queue with two resque workers:
99
+
100
+ +---------------------------------------------------+
101
+ | :serial_work |
102
+ |---------------------------------------------------|
103
+ | | | | |
104
+ | unique_at_runtime_redis_key: | unique_at_runtime_redis_key: | unique_at_runtime_redis_key: | ... |
105
+ | A | A | B | |
106
+ | | | | |
107
+ | job 1 | job 2 | job 3 | |
108
+ +---------------------------------------------------+
109
+ ^
110
+ |
111
+ Possible starvation +-----------+
112
+ for this job and
113
+ subsequent ones
114
+
115
+
116
+ When the first worker grabs job 1, it'll acquire the mutex for processing
117
+ redis\_key A. The second worker tries to grab the next job off the queue but
118
+ is unable to acquire the mutex for redis\_key A so it places job 2 back at the
119
+ head of the :serial\_work queue. Until worker 1 completes job 1 and releases
120
+ the mutex for redis\_key A, no work will be done in this queue.
121
+
122
+ This issue may be avoided by employing dynamic queues,
123
+ http://blog.kabisa.nl/2010/03/16/dynamic-queue-assignment-for-resque-jobs/,
124
+ where the queue is a one to one mapping to the redis\_key.
125
+
126
+ #### Example #3 -- One job running per user-defined attribute with job ordering preserved
127
+
128
+ The secret to preserving job order semantics is to remove critical data from the
129
+ resque job and store data in a separate redis list. Part of a running job's
130
+ responsibility will be to grab data off of the separate redis list needed for it
131
+ to complete its job.
132
+
133
+ +---------------------------------------------------+
134
+ | :serial_work for jobs associated with key A |
135
+ |---------------------------------------------------|
136
+ | data x | data y | data z | ... |
137
+ +---------------------------------------------------+
138
+
139
+ +---------------------------------------------------+
140
+ | :serial_work for jobs associated with key B |
141
+ |---------------------------------------------------|
142
+ | data m | data n | data o | ... |
143
+ +---------------------------------------------------+
144
+
145
+ +---------------------------------------------------+
146
+ | :serial_work |
147
+ |---------------------------------------------------|
148
+ | | | | |
149
+ | unique_at_runtime_redis_key: | unique_at_runtime_redis_key: | unique_at_runtime_redis_key: | ... |
150
+ | A | A | B | |
151
+ | | | | |
152
+ | job 1 | job 2 | job 3 | |
153
+ +---------------------------------------------------+
154
+
155
+ It now doesn't matter whether job 1 and job 2 are re-ordered as whichever goes
156
+ first will perform an atomic pop on the redis list that contains the data needed
157
+ for its job (data x, data y, data z).
158
+
159
+ #### Example #4 -- Requeue interval
160
+
161
+ The behavior when multiple jobs exist in a queue protected by resque-unique_at_runtime
162
+ is for one job to be worked, while the other is continuously dequeued and
163
+ requeued until the first job is finished. This can result in that worker
164
+ process pegging a CPU/core on a worker server. To guard against this, the
165
+ default behavior is to sleep for 1 second before the requeue, which will allow
166
+ the cpu to perform other work.
167
+
168
+ This can be customized using a ```@requeue_interval``` class instance variable
169
+ in your job like so:
170
+
171
+
172
+ require 'resque-unique_at_runtime'
173
+
174
+ class StrictlySerialJob
175
+ extend Resque::Plugins::UniqueAtRuntime
176
+
177
+ @queue = :serial_work
178
+ @requeue_interval = 5 # sleep for 5 seconds before requeueing
179
+
180
+ def self.perform
181
+ # some implementation
182
+ end
183
+ end
184
+
185
+ ## Contributing
186
+
187
+ 1. Fork it
188
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
189
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
190
+ 4. Push to the branch (`git push origin my-new-feature`)
191
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
8
+ task :test => :spec
@@ -0,0 +1,64 @@
1
+ require 'resque-unique_at_runtime/version'
2
+
3
+ module Resque
4
+ module Plugins
5
+ module UniqueAtRuntime
6
+ LOCK_TIMEOUT = 60 * 60 * 24 * 5 # 5 days
7
+
8
+ def lock_timeout
9
+ Time.now.to_i + LOCK_TIMEOUT + 1
10
+ end
11
+
12
+ def requeue_interval
13
+ self.instance_variable_get(:@requeue_interval) || 1
14
+ end
15
+
16
+ # Overwrite this method to uniquely identify which mutex should be used
17
+ # for a resque worker.
18
+ def unique_at_runtime_redis_key(*_)
19
+ "unique_at_runtime:#{@queue}"
20
+ end
21
+
22
+ def can_lock_queue?(*args)
23
+ now = Time.now.to_i
24
+ key = unique_at_runtime_redis_key(*args)
25
+ timeout = lock_timeout
26
+
27
+ # Per http://redis.io/commands/setnx
28
+ return true if Resque.redis.setnx(key, timeout)
29
+ return false if Resque.redis.get(key).to_i > now
30
+ return true if Resque.redis.getset(key, timeout).to_i <= now
31
+ return false
32
+ end
33
+
34
+ def unlock_queue(*args)
35
+ Resque.redis.del(unique_at_runtime_redis_key(*args))
36
+ end
37
+
38
+ def reenqueue(*args)
39
+ Resque.enqueue(self, *args)
40
+ end
41
+
42
+ def before_perform(*args)
43
+ unless can_lock_queue?(*args)
44
+ # Sleep so the CPU's rest
45
+ sleep(requeue_interval)
46
+
47
+ # can't get the lock, so re-enqueue the task
48
+ reenqueue(*args)
49
+
50
+ # and don't perform
51
+ raise Resque::Job::DontPerform
52
+ end
53
+ end
54
+
55
+ def around_perform(*args)
56
+ begin
57
+ yield
58
+ ensure
59
+ unlock_queue(*args)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ module Plugins
3
+ module UniqueAtRuntime
4
+ VERSION = "2.0.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/resque-unique_at_runtime/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Peter H. Boling","Jonathan R. Wallace"]
6
+ gem.email = ["peter.boling@gmail.com","jonathan.wallace@gmail.com"]
7
+ gem.summary = %q{A resque plugin that ensures job uniqueness at runtime.}
8
+ gem.homepage = "http://github.com/pboling/resque-unique_at_runtime"
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "resque-unique_at_runtime"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Resque::Plugins::UniqueAtRuntime::VERSION
16
+ gem.license = "MIT"
17
+
18
+ gem.add_dependency 'resque', '>= 1.2'
19
+ gem.add_development_dependency 'mock_redis'
20
+ gem.add_development_dependency 'rake'
21
+ gem.add_development_dependency 'rspec', '>= 3.0'
22
+ gem.add_development_dependency 'timecop'
23
+
24
+ gem.description = <<desc
25
+ Ensures that for a given queue, only one worker is working on a job at any given time.
26
+
27
+ Example:
28
+
29
+ require 'resque/plugins/unique_at_runtime'
30
+
31
+ class StrictlySerialJob
32
+ extend Resque::Plugins::UniqueAtRuntime
33
+
34
+ @queue = :serial_work
35
+
36
+ def self.perform
37
+ # only one at a time in this block, no parallelism allowed for this
38
+ # particular queue
39
+ end
40
+ end
41
+ desc
42
+ end
@@ -0,0 +1,180 @@
1
+ require 'spec_helper'
2
+
3
+ class SerialJob
4
+ extend Resque::Plugins::UniqueAtRuntime
5
+ @queue = :serial_work
6
+
7
+ def self.perform(*args); end
8
+ end
9
+
10
+ class SerialJobWithCustomRedisKey
11
+ extend Resque::Plugins::UniqueAtRuntime
12
+ @queue = :serial_work
13
+
14
+ def self.unique_at_runtime_redis_key(account_id, *args)
15
+ "unique_at_runtime:#{@queue}:#{account_id}"
16
+ end
17
+
18
+ def self.perform(account_id, *args); end
19
+ end
20
+
21
+ describe Resque::Plugins::UniqueAtRuntime do
22
+ before do
23
+ Resque.redis.flushall
24
+ end
25
+
26
+ describe ".requeue_interval" do
27
+ it "should default to 5" do
28
+ expect(SerialJob.requeue_interval).to eql(1)
29
+ end
30
+
31
+ it "should be overridable with a class instance var" do
32
+ SerialJob.instance_variable_set(:@requeue_interval, 5)
33
+ expect(SerialJob.requeue_interval).to eql(5)
34
+ end
35
+ end
36
+
37
+ describe ".can_lock_queue?" do
38
+ it 'can lock a queue' do
39
+ expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
40
+ end
41
+
42
+ it 'cannot lock an already locked queue' do
43
+ expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
44
+ expect(SerialJob.can_lock_queue?(:serial_work)).to eql(false)
45
+ end
46
+
47
+ it 'cannot lock a queue with active lock' do
48
+ expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
49
+ Timecop.travel(Date.today + 1) do
50
+ expect(SerialJob.can_lock_queue?(:serial_work)).to eql(false)
51
+ end
52
+ end
53
+
54
+ it 'can relock a queue with expired lock' do
55
+ expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
56
+
57
+ Timecop.travel(Date.today + 10) do
58
+ expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
59
+ end
60
+ end
61
+
62
+ it 'solves race condition with getset' do
63
+ expect(SerialJob.can_lock_queue?(:serial_work)).to eql(true)
64
+
65
+ Timecop.travel(Date.today + 10) do
66
+ threads = (1..10).to_a.map {
67
+ Thread.new {
68
+ Thread.current[:locked] = SerialJob.can_lock_queue?(:serial_work)
69
+ }
70
+ }
71
+
72
+ # Only one worker should acquire lock
73
+ locks = threads.map {|t| t.join; t[:locked] }
74
+ expect(locks.count(true)).to eql(1)
75
+ end
76
+ end
77
+ end
78
+
79
+ describe ".perform" do
80
+ before do
81
+ SerialJob.instance_variable_set(:@requeue_interval, 0)
82
+ end
83
+
84
+ describe "using the default redis key" do
85
+ it 'should lock and unlock the queue' do
86
+ job = Resque::Job.new(:serial_work, { 'class' => 'SerialJob', 'args' => %w[account_one job_one] })
87
+
88
+ # job is the first SerialJob to run so it can lock the queue and perform
89
+ expect(SerialJob).to receive(:can_lock_queue?).and_return(true)
90
+
91
+ # but it should also clean up after itself
92
+ expect(SerialJob).to receive(:unlock_queue)
93
+
94
+ job.perform
95
+ end
96
+
97
+ it 'should clean up lock even with catastrophic job failure' do
98
+ job = Resque::Job.new(:serial_work, { 'class' => 'SerialJob', 'args' => %w[account_one job_one] })
99
+
100
+ # job is the first SerialJob to run so it can lock the queue and perform
101
+ expect(SerialJob).to receive(:can_lock_queue?).and_return(true)
102
+
103
+ # but we have a catastrophic job failure
104
+ expect(SerialJob).to receive(:perform).and_raise(Exception)
105
+
106
+ # and still it should clean up after itself
107
+ expect(SerialJob).to receive(:unlock_queue)
108
+
109
+ # unfortunately, the job will be lost but resque doesn't guarantee jobs
110
+ # aren't lost
111
+ expect { job.perform }.to raise_error(Exception)
112
+ end
113
+
114
+ it 'should place self at the end of the queue if unable to acquire the lock' do
115
+ job1_payload = %w[account_one job_one]
116
+ job2_payload = %w[account_one job_two]
117
+ Resque::Job.create(:serial_work, 'SerialJob', job1_payload)
118
+ Resque::Job.create(:serial_work, 'SerialJob', job2_payload)
119
+
120
+ expect(SerialJob).to receive(:can_lock_queue?).and_return(false)
121
+
122
+ # perform returns false when DontPerform exception is raised in
123
+ # before_perform callback
124
+ job1 = Resque.reserve(:serial_work)
125
+ expect(job1.perform).to eql(false)
126
+
127
+ first_queue_element = Resque.reserve(:serial_work)
128
+ expect(first_queue_element.payload["args"]).to eql([job2_payload])
129
+ end
130
+ end
131
+
132
+ describe "with a custom unique_at_runtime_redis_key" do
133
+ it 'should lock and unlock the queue' do
134
+ job = Resque::Job.new(:serial_work, { 'class' => 'SerialJobWithCustomRedisKey', 'args' => %w[account_one job_one] })
135
+
136
+ # job is the first SerialJobWithCustomRedisKey to run so it can lock the queue and perform
137
+ expect(SerialJobWithCustomRedisKey).to receive(:can_lock_queue?).and_return(true)
138
+
139
+ # but it should also clean up after itself
140
+ expect(SerialJobWithCustomRedisKey).to receive(:unlock_queue)
141
+
142
+ job.perform
143
+ end
144
+
145
+ it 'should clean up lock even with catastrophic job failure' do
146
+ job = Resque::Job.new(:serial_work, { 'class' => 'SerialJobWithCustomRedisKey', 'args' => %w[account_one job_one] })
147
+
148
+ # job is the first SerialJobWithCustomRedisKey to run so it can lock the queue and perform
149
+ expect(SerialJobWithCustomRedisKey).to receive(:can_lock_queue?).and_return(true)
150
+
151
+ # but we have a catastrophic job failure
152
+ expect(SerialJobWithCustomRedisKey).to receive(:perform).and_raise(Exception)
153
+
154
+ # and still it should clean up after itself
155
+ expect(SerialJobWithCustomRedisKey).to receive(:unlock_queue)
156
+
157
+ # unfortunately, the job will be lost but resque doesn't guarantee jobs
158
+ # aren't lost
159
+ expect { job.perform }.to raise_error(Exception)
160
+ end
161
+
162
+ it 'should place self at the end of the queue if unable to acquire the lock' do
163
+ job1_payload = %w[account_one job_one]
164
+ job2_payload = %w[account_one job_two]
165
+ Resque::Job.create(:serial_work, 'SerialJobWithCustomRedisKey', job1_payload)
166
+ Resque::Job.create(:serial_work, 'SerialJobWithCustomRedisKey', job2_payload)
167
+
168
+ expect(SerialJobWithCustomRedisKey).to receive(:can_lock_queue?).and_return(false)
169
+
170
+ # perform returns false when DontPerform exception is raised in
171
+ # before_perform callback
172
+ job1 = Resque.reserve(:serial_work)
173
+ expect(job1.perform).to eql(false)
174
+
175
+ first_queue_element = Resque.reserve(:serial_work)
176
+ expect(first_queue_element.payload["args"]).to eql([job2_payload])
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,14 @@
1
+ require 'rspec'
2
+
3
+ require 'mock_redis'
4
+ require 'resque'
5
+ require 'timecop'
6
+
7
+ # This gem
8
+ require 'resque-unique_at_runtime'
9
+
10
+ RSpec.configure do |config|
11
+ config.before(:suite) do
12
+ Resque.redis = MockRedis.new
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque-unique_at_runtime
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter H. Boling
8
+ - Jonathan R. Wallace
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-10-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: resque
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '1.2'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '1.2'
28
+ - !ruby/object:Gem::Dependency
29
+ name: mock_redis
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: timecop
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ description: |
85
+ Ensures that for a given queue, only one worker is working on a job at any given time.
86
+
87
+ Example:
88
+
89
+ require 'resque/plugins/unique_at_runtime'
90
+
91
+ class StrictlySerialJob
92
+ extend Resque::Plugins::UniqueAtRuntime
93
+
94
+ @queue = :serial_work
95
+
96
+ def self.perform
97
+ # only one at a time in this block, no parallelism allowed for this
98
+ # particular queue
99
+ end
100
+ end
101
+ email:
102
+ - peter.boling@gmail.com
103
+ - jonathan.wallace@gmail.com
104
+ executables: []
105
+ extensions: []
106
+ extra_rdoc_files: []
107
+ files:
108
+ - ".gitignore"
109
+ - ".travis.yml"
110
+ - Gemfile
111
+ - LICENSE
112
+ - README.md
113
+ - Rakefile
114
+ - lib/resque-unique_at_runtime.rb
115
+ - lib/resque-unique_at_runtime/version.rb
116
+ - resque-unique_at_runtime.gemspec
117
+ - spec/lib/unique_at_runtime_spec.rb
118
+ - spec/spec_helper.rb
119
+ homepage: http://github.com/pboling/resque-unique_at_runtime
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.6.12
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: A resque plugin that ensures job uniqueness at runtime.
143
+ test_files:
144
+ - spec/lib/unique_at_runtime_spec.rb
145
+ - spec/spec_helper.rb