activejob-locking 0.1.0
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.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/HISTORY.md +3 -0
- data/LICENSE +20 -0
- data/README.md +245 -0
- data/Rakefile +21 -0
- data/lib/activejob/locking/adapters/base.rb +32 -0
- data/lib/activejob/locking/adapters/memory.rb +60 -0
- data/lib/activejob/locking/adapters/redis-semaphore.rb +25 -0
- data/lib/activejob/locking/adapters/redlock.rb +26 -0
- data/lib/activejob/locking/adapters/suo-redis.rb +25 -0
- data/lib/activejob/locking/base.rb +41 -0
- data/lib/activejob/locking/enqueue.rb +20 -0
- data/lib/activejob/locking/options.rb +46 -0
- data/lib/activejob/locking/perform.rb +23 -0
- data/lib/activejob-locking.rb +23 -0
- data/test/enqueue_tests.rb +74 -0
- data/test/jobs/enqueue_drop_job.rb +15 -0
- data/test/jobs/enqueue_wait_job.rb +15 -0
- data/test/jobs/enqueue_wait_large_timeout_job.rb +16 -0
- data/test/jobs/enqueue_wait_timeout_job.rb +16 -0
- data/test/jobs/perform_serially_job.rb +15 -0
- data/test/jobs/perform_serially_large_timeout_job.rb +16 -0
- data/test/perform_tests.rb +37 -0
- data/test/test_enqueue_memory.rb +45 -0
- data/test/test_enqueue_redis_semaphore.rb +12 -0
- data/test/test_enqueue_redlock.rb +14 -0
- data/test/test_enqueue_suo_redis.rb +12 -0
- data/test/test_helper.rb +22 -0
- data/test/test_perform_memory.rb +14 -0
- data/test/test_perform_redis_semaphore.rb +12 -0
- data/test/test_perform_redlock.rb +14 -0
- data/test/test_perform_suo_redis.rb +12 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cd8286a266d71bc225867b645c8e6839d492d1a3
|
4
|
+
data.tar.gz: ea9464a6fcd179539542168ab5f40f6a86e76d1d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a20e9295791ae6e571afda6c7d243003cd601c1bf1f403bb757ce5c2022cbe34ecb0b6f7d64ea243021078b9cfa9e783bd4b1b0f6ec374e109d3a7d88a83415d
|
7
|
+
data.tar.gz: 25ea633bf9c6386a7d8e98b3008cb8cc4a85a865637165acfa0f0ea12b283269257d04b185114609d55f616ce7473b5fdbe21989ff222cef6a193342483f9a92
|
data/Gemfile
ADDED
data/HISTORY.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2017 Charlie Savage
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
Software), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
ActiveJob Locking
|
2
|
+
===================
|
3
|
+
|
4
|
+
[](http://travis-ci.org/cfis/activejob-locking)
|
5
|
+
[](http://badge.fury.io/rb/activejob-locking)
|
6
|
+
|
7
|
+
activejob-locking lets you control how ActiveJobs are enqueued and performed:
|
8
|
+
|
9
|
+
* Allow only one job to be enqueued at a time (based on a lock_id)
|
10
|
+
* Allow only one job to be peformed at a time (also based on a lock_id)
|
11
|
+
|
12
|
+
There are many other similar gems including [resque-lock-timeout](https://github.com/lantins/resque-lock-timeout),
|
13
|
+
[activejob-traffic-control](https://github.com/nickelser/activejob-traffic_control), [activejob-lock](https://github.com/idolweb/activejob-lock),
|
14
|
+
[activejob-locks](https://github.com/erickrause/activejob-locks). What is different about this gem is that it
|
15
|
+
is agnostic on the locking mechanism. In the same way that ActiveJob works with many apapters, ActiveJob Locking
|
16
|
+
works with a variety of locking gems.
|
17
|
+
|
18
|
+
Installation
|
19
|
+
------------
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'activejob-locking'
|
25
|
+
```
|
26
|
+
|
27
|
+
Enqueueing
|
28
|
+
------------
|
29
|
+
Sometime you only want to enqueue one instance of a job. No other similar job should be enqueued until the first one
|
30
|
+
is completed.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class EnqueueDropJob < ActiveJob::Base
|
34
|
+
include ActiveJob::Locking::Enqueue
|
35
|
+
|
36
|
+
# Make sure the lock_key is always the same
|
37
|
+
def lock_key
|
38
|
+
self.class.name
|
39
|
+
end
|
40
|
+
|
41
|
+
def perform
|
42
|
+
# do some work
|
43
|
+
end
|
44
|
+
end
|
45
|
+
```
|
46
|
+
Only one instance of this job will ever be enqueued. If an additional job is enqueued, it will either be dropped and
|
47
|
+
never be enqueued or it will wait to the first job is performed. That is controlled by the job
|
48
|
+
[options](##options) described below.
|
49
|
+
|
50
|
+
|
51
|
+
Performing
|
52
|
+
------------
|
53
|
+
Sometime you only want to perform one instance of a job at a time. No other similar job should be performed until the first one
|
54
|
+
is completed.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class EnqueueDropJob < ActiveJob::Base
|
58
|
+
include ActiveJob::Locking::Perform
|
59
|
+
|
60
|
+
# Make sure the lock_key is always the same
|
61
|
+
def lock_key
|
62
|
+
self.class.name
|
63
|
+
end
|
64
|
+
|
65
|
+
def perform
|
66
|
+
# do some work
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
Only one instance of this job will ever be performed. If an additional job is enqueued, it will wait in its que until
|
71
|
+
to the first job is performed.
|
72
|
+
|
73
|
+
Locking
|
74
|
+
------------
|
75
|
+
Locks are used to control how jobs are enqueued and performed. The idea is that locks are stored in a distributed
|
76
|
+
system such as [Redis](https://redis.io/) or [Memcached](https://memcached.org/) so they can be used by
|
77
|
+
multiple servers to coordinate the enqueueing and performing of jobs.
|
78
|
+
|
79
|
+
The ActiveJob Locking gem does not include a locking implementation. Instead it provides adapters for
|
80
|
+
distributed locking gems.
|
81
|
+
|
82
|
+
Currently three gems are supported:
|
83
|
+
|
84
|
+
* [redis-semaphore](https://github.com/dv/redis-semaphore)
|
85
|
+
|
86
|
+
* [suo](https://github.com/nickelser/suo)
|
87
|
+
|
88
|
+
* [redlock-rb](https://github.com/leandromoreira/redlock-rb)
|
89
|
+
|
90
|
+
If you would like to have an additional locking mechanism supported, please feel free to send in a pull request.
|
91
|
+
|
92
|
+
Please see the [options](##options) section below on how to specify a locking adapter.
|
93
|
+
|
94
|
+
|
95
|
+
Lock Key
|
96
|
+
---------
|
97
|
+
|
98
|
+
Notice that the code samples above include a `lock_key` method. The return value of this method is used by the
|
99
|
+
gem to create locks behind the scenes. Thus it holds the key (pun intended) to controlling how jobs are enqueued
|
100
|
+
and performed.
|
101
|
+
|
102
|
+
By default the key is defined as:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
def lock_key
|
106
|
+
[self.class.name, serialize_arguments(self.arguments)].join('/')
|
107
|
+
end
|
108
|
+
```
|
109
|
+
Thus it has the format `<job class name>/<serialized_job_arguments>`
|
110
|
+
|
111
|
+
To use this gem, you will want to override this method per job.
|
112
|
+
|
113
|
+
### Examples
|
114
|
+
|
115
|
+
Allow only one job per queue to be enqueued or performed:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
def lock_key
|
119
|
+
self.queue
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
Allow only one instance of a job class to be enqueued of performed:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
def lock_key
|
127
|
+
self.class.name
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
Options
|
132
|
+
-------
|
133
|
+
The locking behavior can be dramatically changed by tweaking various options. There is a global set of options
|
134
|
+
available at:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
ActiveJob::Locking.options
|
138
|
+
```
|
139
|
+
This should be updated using a Rails initializer. Each job class can override invidual options as it sees fit.
|
140
|
+
|
141
|
+
### Adapter
|
142
|
+
|
143
|
+
Use the adapter option to specify which locking gem to use.
|
144
|
+
|
145
|
+
Globally update:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::SuoRedis
|
149
|
+
```
|
150
|
+
Locally update:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
class ExampleJob < ActiveJob::Base
|
154
|
+
include ActiveJob::Locking::Perform
|
155
|
+
|
156
|
+
self.adapter = ActiveJob::Locking::Adapters::SuoRedis
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
### Host
|
161
|
+
|
162
|
+
The host(s) of the distributed system. This format will be dependent on the locking gem, so please read its
|
163
|
+
documentation.
|
164
|
+
|
165
|
+
Globally update:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
ActiveJob::Locking.options.hosts = 'localhost'
|
169
|
+
```
|
170
|
+
Locally update:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
class ExampleJob < ActiveJob::Base
|
174
|
+
include ActiveJob::Locking::Perform
|
175
|
+
|
176
|
+
self.hosts = 'localhost'
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
### Time
|
181
|
+
|
182
|
+
The is the time to live for any acquired locks. For most locking gems this is mapped to their concept of "stale" locks.
|
183
|
+
That means that if an attempt is made to access the lock after it is expired, it will be considered unlocked. That is in
|
184
|
+
contrast to aggressively removing locks for running jobs even if no other job has requested them.
|
185
|
+
|
186
|
+
The value is specified in seconds and defaults to 100.
|
187
|
+
|
188
|
+
Globally update:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
ActiveJob::Locking.options.time = 100
|
192
|
+
```
|
193
|
+
Locally update (notice the different method name to avoid potential conflicts):
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
class ExampleJob < ActiveJob::Base
|
197
|
+
include ActiveJob::Locking::Perform
|
198
|
+
|
199
|
+
self.lock_time = 100
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
### Timeout
|
204
|
+
|
205
|
+
The is the timeout for acquiring a lock. The value is specified in seconds and defaults to 1. It must
|
206
|
+
be greater than zero and cannot be nil.
|
207
|
+
|
208
|
+
Globally update:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
ActiveJob::Locking.options.timeout = 1
|
212
|
+
```
|
213
|
+
Locally update (notice the different method name to avoid potential conflicts):
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
class ExampleJob < ActiveJob::Base
|
217
|
+
include ActiveJob::Locking::Enqueue
|
218
|
+
|
219
|
+
self.lock_acquire_timeout= = 1
|
220
|
+
end
|
221
|
+
```
|
222
|
+
This greatly influences how enqueuing behavior works. If the timeout is short, then jobs that are waiting to
|
223
|
+
be enqueued are dropped and the before_enqueue callback will fail. If the timeout is infinite, then jobs will wait
|
224
|
+
in turn to get enqueued. If the timeout is somewhere in between then it will depend on how long the jobs
|
225
|
+
take to execute.
|
226
|
+
|
227
|
+
### AdapterOptions
|
228
|
+
|
229
|
+
This is a hash table of options that should be sent to the lock gem when it is instantiated. Read the lock
|
230
|
+
gems documentation to find appropriate values.
|
231
|
+
|
232
|
+
Globally update:
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
ActiveJob::Locking.options.adapter_options = {}
|
236
|
+
```
|
237
|
+
Locally update (notice the different method name to avoid potential conflicts):
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
class ExampleJob < ActiveJob::Base
|
241
|
+
include ActiveJob::Locking::Enqueue
|
242
|
+
|
243
|
+
self.adapter_options = {}
|
244
|
+
end
|
245
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rubygems/package_task'
|
6
|
+
|
7
|
+
# Set global variable so other tasks can access them
|
8
|
+
::PROJECT_ROOT = File.expand_path(".")
|
9
|
+
::GEM_NAME = 'activejob-locking'
|
10
|
+
|
11
|
+
# Read the spec file
|
12
|
+
spec = Gem::Specification.load("#{GEM_NAME}.gemspec")
|
13
|
+
|
14
|
+
# Setup Rake tasks for managing the gem
|
15
|
+
Gem::PackageTask.new(spec).define
|
16
|
+
|
17
|
+
desc 'Run unit tests.'
|
18
|
+
Rake::TestTask.new(:test) do |task|
|
19
|
+
task.test_files = FileList['test/test_*.rb']
|
20
|
+
task.verbose = true
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module Locking
|
3
|
+
module Adapters
|
4
|
+
class Base
|
5
|
+
attr_reader :key, :options, :lock_manager
|
6
|
+
attr_accessor :lock_token
|
7
|
+
|
8
|
+
def initialize(key, options)
|
9
|
+
@key = key
|
10
|
+
@options = options
|
11
|
+
@lock_manager = self.create_lock_manager
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_lock_manager
|
15
|
+
raise('Subclass must implement')
|
16
|
+
end
|
17
|
+
|
18
|
+
def lock
|
19
|
+
raise('Subclass must implement')
|
20
|
+
end
|
21
|
+
|
22
|
+
def unlock
|
23
|
+
raise('Subclass must implement')
|
24
|
+
end
|
25
|
+
|
26
|
+
def refresh_lock!(refresh)
|
27
|
+
raise('Subclass must implement')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module Locking
|
3
|
+
module Adapters
|
4
|
+
class Memory < Base
|
5
|
+
attr_reader :timeout
|
6
|
+
|
7
|
+
@hash = Hash.new
|
8
|
+
@mutex = Mutex.new
|
9
|
+
|
10
|
+
def self.lock(key)
|
11
|
+
@mutex.synchronize do
|
12
|
+
if @hash[key]
|
13
|
+
false
|
14
|
+
else
|
15
|
+
@hash[key] = Time.now
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.unlock(key)
|
21
|
+
@mutex.synchronize do
|
22
|
+
@hash.delete(key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.locked?(key)
|
27
|
+
@mutex.synchronize do
|
28
|
+
@hash.include?(key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.reset
|
33
|
+
@mutex.synchronize do
|
34
|
+
@hash = Hash.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_lock_manager
|
39
|
+
end
|
40
|
+
|
41
|
+
def lock
|
42
|
+
finish = Time.now + self.options.timeout
|
43
|
+
sleep_time = [5, self.options.timeout / 5].min
|
44
|
+
|
45
|
+
begin
|
46
|
+
lock = self.class.lock(key)
|
47
|
+
return lock if lock
|
48
|
+
sleep(sleep_time)
|
49
|
+
end while Time.now < finish
|
50
|
+
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
|
54
|
+
def unlock
|
55
|
+
self.class.unlock(self.key)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'redis-semaphore'
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Locking
|
5
|
+
module Adapters
|
6
|
+
class RedisSemaphore < Base
|
7
|
+
def create_lock_manager
|
8
|
+
mapped_options = {host: self.options.hosts,
|
9
|
+
stale_client_timeout: self.options.time}.merge(self.options.adapter_options)
|
10
|
+
|
11
|
+
Redis::Semaphore.new(self.key, mapped_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def lock
|
15
|
+
self.lock_token = self.lock_manager.lock(self.options.timeout)
|
16
|
+
end
|
17
|
+
|
18
|
+
def unlock
|
19
|
+
self.lock_manager.signal(self.lock_token)
|
20
|
+
self.lock_token = nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'redlock'
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Locking
|
5
|
+
module Adapters
|
6
|
+
class Redlock < Base
|
7
|
+
def create_lock_manager
|
8
|
+
mapped_options = self.options.adapter_options
|
9
|
+
mapped_options[:retry_count] = ::Redlock::Client::DEFAULT_RETRY_COUNT
|
10
|
+
mapped_options[:retry_delay] = 2000 * ((self.options.timeout || 2**32) / (::Redlock::Client::DEFAULT_RETRY_COUNT * 1.0))
|
11
|
+
|
12
|
+
::Redlock::Client.new(Array(self.options.hosts), mapped_options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def lock
|
16
|
+
self.lock_token = self.lock_manager.lock(self.key, self.options.time * 1000)
|
17
|
+
end
|
18
|
+
|
19
|
+
def unlock
|
20
|
+
self.lock_manager.unlock(self.lock_token)
|
21
|
+
self.lock_token = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'suo'
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Locking
|
5
|
+
module Adapters
|
6
|
+
class SuoRedis < Base
|
7
|
+
def create_lock_manager
|
8
|
+
mapped_options = {connection: {host: self.options.hosts},
|
9
|
+
stale_lock_expiration: self.options.time,
|
10
|
+
acquisition_timeout: self.options.timeout}
|
11
|
+
|
12
|
+
Suo::Client::Redis.new(self.key, mapped_options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def lock
|
16
|
+
self.lock_token = self.lock_manager.lock
|
17
|
+
end
|
18
|
+
|
19
|
+
def unlock
|
20
|
+
self.lock_manager.unlock(self.lock_token)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module Locking
|
3
|
+
module Base
|
4
|
+
extend ::ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def lock_options
|
8
|
+
@lock_options ||= ActiveJob::Locking::Options.new
|
9
|
+
end
|
10
|
+
delegate :adapter, :hosts, :lock_time, :lock_acquire_timeout, :adapter_options, to: :lock_options
|
11
|
+
delegate :adapter=, :hosts=, :lock_time=, :lock_acquire_timeout=, :adapter_options=, to: :lock_options
|
12
|
+
end
|
13
|
+
|
14
|
+
included do
|
15
|
+
# We need to serialize the lock token because it could be released in a different process
|
16
|
+
def serialize
|
17
|
+
result = super
|
18
|
+
result = result.merge('lock_token' => self.adapter.lock_token) if self.adapter.lock_token
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def deserialize(job_data)
|
23
|
+
super
|
24
|
+
self.adapter.lock_token = job_data['lock_token']
|
25
|
+
end
|
26
|
+
|
27
|
+
def lock_key
|
28
|
+
[self.class.name, serialize_arguments(self.arguments)].join('/')
|
29
|
+
end
|
30
|
+
|
31
|
+
def adapter
|
32
|
+
# Merge local and global options
|
33
|
+
merged_options = ActiveJob::Locking.options.dup.merge(self.class.lock_options)
|
34
|
+
|
35
|
+
# Remember the lock might be acquired in one process and released in another
|
36
|
+
@adapter ||= merged_options.adapter.new(self.lock_key, merged_options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module Locking
|
3
|
+
module Enqueue
|
4
|
+
extend ::ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ::ActiveJob::Locking::Base
|
8
|
+
|
9
|
+
before_enqueue do |job|
|
10
|
+
lock = self.adapter.lock
|
11
|
+
throw :abort unless lock
|
12
|
+
end
|
13
|
+
|
14
|
+
after_perform do |job|
|
15
|
+
self.adapter.unlock
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Locking
|
5
|
+
class Options
|
6
|
+
attr_accessor :adapter
|
7
|
+
attr_accessor :hosts
|
8
|
+
attr_accessor :time
|
9
|
+
attr_accessor :timeout
|
10
|
+
attr_accessor :adapter_options
|
11
|
+
|
12
|
+
alias :lock_time :time
|
13
|
+
alias :lock_time= :time=
|
14
|
+
alias :lock_acquire_timeout :timeout
|
15
|
+
alias :lock_acquire_timeout= :timeout=
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
@adapter = options[:adapter]
|
19
|
+
@hosts = options[:hosts]
|
20
|
+
@time = options[:time]
|
21
|
+
@timeout = options[:timeout]
|
22
|
+
@adapter_options = options[:adapter_options]
|
23
|
+
end
|
24
|
+
|
25
|
+
def timeout=(value)
|
26
|
+
if value.nil?
|
27
|
+
raise(ArgumentError, 'Lock timeout must be set')
|
28
|
+
elsif value == 0
|
29
|
+
raise(ArgumentError, 'Lock timeout must be greater than zero')
|
30
|
+
else
|
31
|
+
@timeout = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def merge(other)
|
36
|
+
result = self.dup
|
37
|
+
result.adapter = other.adapter if other.adapter
|
38
|
+
result.hosts = other.hosts if other.hosts
|
39
|
+
result.time = other.time if other.time
|
40
|
+
result.timeout = other.timeout if other.timeout
|
41
|
+
result.adapter_options = other.adapter_options if other.adapter_options
|
42
|
+
result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module Locking
|
3
|
+
module Perform
|
4
|
+
extend ::ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ::ActiveJob::Locking::Base
|
8
|
+
|
9
|
+
around_perform do |job, block|
|
10
|
+
if self.adapter.lock
|
11
|
+
begin
|
12
|
+
block.call
|
13
|
+
ensure
|
14
|
+
self.adapter.unlock
|
15
|
+
end
|
16
|
+
else
|
17
|
+
self.class.set(wait: 5.seconds).perform_later(job)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'activejob/locking/adapters/base'
|
2
|
+
require 'activejob/locking/adapters/memory'
|
3
|
+
|
4
|
+
require 'activejob/locking/base'
|
5
|
+
require 'activejob/locking/enqueue'
|
6
|
+
require 'activejob/locking/perform'
|
7
|
+
|
8
|
+
require 'activejob/locking/options'
|
9
|
+
|
10
|
+
module ActiveJob
|
11
|
+
module Locking
|
12
|
+
@options = ActiveJob::Locking::Options.new(adapter: ActiveJob::Locking::Adapters::Memory,
|
13
|
+
hosts: 'localhost',
|
14
|
+
time: 100,
|
15
|
+
timeout: 1,
|
16
|
+
adapter_options: {})
|
17
|
+
|
18
|
+
def self.options
|
19
|
+
@options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module EnqueueTests
|
4
|
+
def test_drop
|
5
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
6
|
+
|
7
|
+
start_time = Time.now
|
8
|
+
sleep_time = 2
|
9
|
+
threads = 2.times.map do |i|
|
10
|
+
Thread.new do
|
11
|
+
EnqueueDropJob.perform_later(i, sleep_time)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
threads.each {|thread| thread.join}
|
16
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
17
|
+
assert_equal(1, ActiveJob::Base.queue_adapter.performed_jobs.count)
|
18
|
+
assert(Time.now - start_time > (1 * sleep_time))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_wait
|
22
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
23
|
+
|
24
|
+
start_time = Time.now
|
25
|
+
sleep_time = 2
|
26
|
+
threads = 3.times.map do |i|
|
27
|
+
Thread.new do
|
28
|
+
EnqueueWaitJob.perform_later(i, sleep_time)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
threads.each {|thread| thread.join}
|
33
|
+
|
34
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
35
|
+
assert_equal(threads.count, ActiveJob::Base.queue_adapter.performed_jobs.count)
|
36
|
+
assert(Time.now - start_time > (threads.count * sleep_time))
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_wait_large_timeout
|
40
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
41
|
+
|
42
|
+
start_time = Time.now
|
43
|
+
sleep_time = 2 * EnqueueWaitTimeoutJob.lock_acquire_timeout
|
44
|
+
threads = 3.times.map do |i|
|
45
|
+
Thread.new do
|
46
|
+
EnqueueWaitTimeoutJob.perform_later(i, sleep_time)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
threads.each {|thread| thread.join}
|
51
|
+
|
52
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
53
|
+
assert_equal(1, ActiveJob::Base.queue_adapter.performed_jobs.count)
|
54
|
+
assert(Time.now - start_time > (1 * sleep_time))
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_wait_timeout
|
58
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
59
|
+
|
60
|
+
start_time = Time.now
|
61
|
+
sleep_time = 0.2 * EnqueueWaitLargeTimeoutJob.lock_acquire_timeout
|
62
|
+
threads = 3.times.map do |i|
|
63
|
+
Thread.new do
|
64
|
+
EnqueueWaitLargeTimeoutJob.perform_later(i, sleep_time)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
threads.each {|thread| thread.join}
|
69
|
+
|
70
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
71
|
+
assert_equal(threads.count, ActiveJob::Base.queue_adapter.performed_jobs.count)
|
72
|
+
assert(Time.now - start_time > (threads.count * sleep_time))
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class EnqueueDropJob < ActiveJob::Base
|
2
|
+
include ActiveJob::Locking::Enqueue
|
3
|
+
|
4
|
+
self.lock_acquire_timeout = 0.1
|
5
|
+
|
6
|
+
# We want the job ids to be all the same for testing
|
7
|
+
def lock_key
|
8
|
+
self.class.name
|
9
|
+
end
|
10
|
+
|
11
|
+
# Pass in index so we can distinguish different jobs
|
12
|
+
def perform(index, sleep_time)
|
13
|
+
sleep(sleep_time)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class EnqueueWaitJob < ActiveJob::Base
|
2
|
+
include ActiveJob::Locking::Enqueue
|
3
|
+
|
4
|
+
self.lock_acquire_timeout = 1.hour
|
5
|
+
|
6
|
+
# We want the job ids to be all the same for testing
|
7
|
+
def lock_key
|
8
|
+
self.class.name
|
9
|
+
end
|
10
|
+
|
11
|
+
# Pass in index so we can distinguish different jobs
|
12
|
+
def perform(index, sleep_time)
|
13
|
+
sleep(sleep_time)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class EnqueueWaitLargeTimeoutJob < ActiveJob::Base
|
2
|
+
include ActiveJob::Locking::Enqueue
|
3
|
+
|
4
|
+
# Wait for 10 seconds to get a lock
|
5
|
+
self.lock_acquire_timeout = 10
|
6
|
+
|
7
|
+
# We want the job ids to be all the same for testing
|
8
|
+
def lock_key
|
9
|
+
self.class.name
|
10
|
+
end
|
11
|
+
|
12
|
+
# Pass in index so we can distinguish different jobs
|
13
|
+
def perform(index, sleep_time)
|
14
|
+
sleep(sleep_time)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class EnqueueWaitTimeoutJob < ActiveJob::Base
|
2
|
+
include ActiveJob::Locking::Enqueue
|
3
|
+
|
4
|
+
# Wait for 1 second to get a lock
|
5
|
+
self.lock_acquire_timeout = 1
|
6
|
+
|
7
|
+
# We want the job ids to be all the same for testing
|
8
|
+
def lock_key
|
9
|
+
self.class.name
|
10
|
+
end
|
11
|
+
|
12
|
+
# Pass in index so we can distinguish different jobs
|
13
|
+
def perform(index, sleep_time)
|
14
|
+
sleep(sleep_time)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class PerformSeriallyJob < ActiveJob::Base
|
2
|
+
include ActiveJob::Locking::Perform
|
3
|
+
|
4
|
+
self.lock_acquire_timeout = 1.hour
|
5
|
+
|
6
|
+
# We want the job ids to be all the same for testing
|
7
|
+
def lock_key
|
8
|
+
self.class.name
|
9
|
+
end
|
10
|
+
|
11
|
+
# Pass in index so we can distinguish different jobs
|
12
|
+
def perform(index, sleep_time)
|
13
|
+
sleep(sleep_time)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class PerformSeriallyLargeTimeoutJob < ActiveJob::Base
|
2
|
+
include ActiveJob::Locking::Enqueue
|
3
|
+
|
4
|
+
# Wait for 10 seconds to get a lock
|
5
|
+
self.lock_acquire_timeout = 10
|
6
|
+
|
7
|
+
# We want the job ids to be all the same for testing
|
8
|
+
def lock_key
|
9
|
+
self.class.name
|
10
|
+
end
|
11
|
+
|
12
|
+
# Pass in index so we can distinguish different jobs
|
13
|
+
def perform(index, sleep_time)
|
14
|
+
sleep(sleep_time)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module PerformTests
|
4
|
+
def test_serialize
|
5
|
+
start_time = Time.now
|
6
|
+
sleep_time = 2
|
7
|
+
threads = 3.times.map do |i|
|
8
|
+
Thread.new do
|
9
|
+
PerformSeriallyJob.perform_later(i, sleep_time)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
threads.each {|thread| thread.join}
|
14
|
+
|
15
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
16
|
+
assert_equal(threads.count, ActiveJob::Base.queue_adapter.performed_jobs.count)
|
17
|
+
assert(Time.now - start_time > (threads.count * sleep_time))
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_wait_large_timeout
|
21
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
22
|
+
|
23
|
+
start_time = Time.now
|
24
|
+
sleep_time = 2 * EnqueueWaitTimeoutJob.lock_acquire_timeout
|
25
|
+
threads = 3.times.map do |i|
|
26
|
+
Thread.new do
|
27
|
+
EnqueueWaitTimeoutJob.perform_later(i, sleep_time)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
threads.each {|thread| thread.join}
|
32
|
+
|
33
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
34
|
+
assert_equal(1, ActiveJob::Base.queue_adapter.performed_jobs.count)
|
35
|
+
assert(Time.now - start_time > (1 * sleep_time))
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative('./enqueue_tests')
|
2
|
+
|
3
|
+
class EnqueueMemoryTest < MiniTest::Test
|
4
|
+
include EnqueueTests
|
5
|
+
|
6
|
+
def setup
|
7
|
+
ActiveJob::Base.queue_adapter = :test
|
8
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
9
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::Memory
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_enqueue_one
|
13
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = false
|
14
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::Memory
|
15
|
+
|
16
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = false
|
17
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
18
|
+
|
19
|
+
sleep_time = 2
|
20
|
+
threads = 3.times.map do |i|
|
21
|
+
Thread.new do
|
22
|
+
EnqueueDropJob.perform_later(i, sleep_time)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
threads.each {|thread| thread.join}
|
27
|
+
assert_equal(1, ActiveJob::Base.queue_adapter.enqueued_jobs.count)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_perform_one
|
31
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::Memory
|
32
|
+
|
33
|
+
assert_equal(0, ActiveJob::Base.queue_adapter.performed_jobs.count)
|
34
|
+
|
35
|
+
sleep_time = 1
|
36
|
+
threads = 3.times.map do |i|
|
37
|
+
Thread.new do
|
38
|
+
EnqueueDropJob.perform_later(i, sleep_time)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
threads.each {|thread| thread.join}
|
43
|
+
assert_equal(1, ActiveJob::Base.queue_adapter.performed_jobs.count)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative('./enqueue_tests')
|
2
|
+
|
3
|
+
class EnqueueRedisSemaphoreTest < MiniTest::Test
|
4
|
+
include EnqueueTests
|
5
|
+
|
6
|
+
def setup
|
7
|
+
redis_reset
|
8
|
+
ActiveJob::Base.queue_adapter = :test
|
9
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
10
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::RedisSemaphore
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative('./enqueue_tests')
|
2
|
+
|
3
|
+
class EnqueueRedlockTest < MiniTest::Test
|
4
|
+
include EnqueueTests
|
5
|
+
|
6
|
+
def setup
|
7
|
+
redis_reset
|
8
|
+
|
9
|
+
ActiveJob::Base.queue_adapter = :test
|
10
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
11
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::Redlock
|
12
|
+
ActiveJob::Locking.options.hosts = Redlock::Client::DEFAULT_REDIS_URLS
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative('./enqueue_tests')
|
2
|
+
|
3
|
+
class EnqueueSuoRedisTest < MiniTest::Test
|
4
|
+
include EnqueueTests
|
5
|
+
|
6
|
+
def setup
|
7
|
+
redis_reset
|
8
|
+
ActiveJob::Base.queue_adapter = :test
|
9
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
10
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::SuoRedis
|
11
|
+
end
|
12
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.require(:default, :test)
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
# To make debugging easier, test within this source tree versus an installed gem
|
7
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
|
8
|
+
require 'activejob-locking'
|
9
|
+
|
10
|
+
require 'activejob/locking/adapters/redis-semaphore'
|
11
|
+
require 'activejob/locking/adapters/redlock'
|
12
|
+
require 'activejob/locking/adapters/suo-redis'
|
13
|
+
|
14
|
+
require_relative './jobs/enqueue_drop_job'
|
15
|
+
require_relative './jobs/enqueue_wait_job'
|
16
|
+
require_relative './jobs/enqueue_wait_timeout_job'
|
17
|
+
require_relative './jobs/enqueue_wait_large_timeout_job'
|
18
|
+
require_relative './jobs/perform_serially_job'
|
19
|
+
|
20
|
+
def redis_reset
|
21
|
+
Kernel.system('redis-cli FLUSHALL')
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative('./perform_tests')
|
2
|
+
|
3
|
+
class PerformMemory < MiniTest::Test
|
4
|
+
include PerformTests
|
5
|
+
|
6
|
+
def setup
|
7
|
+
redis_reset
|
8
|
+
|
9
|
+
ActiveJob::Base.queue_adapter = :test
|
10
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
11
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::Memory
|
12
|
+
ActiveJob::Locking.options.hosts = Redlock::Client::DEFAULT_REDIS_URLS
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative('./perform_tests')
|
2
|
+
|
3
|
+
class EnqueueRedisSemaphoreTest < MiniTest::Test
|
4
|
+
include PerformTests
|
5
|
+
|
6
|
+
def setup
|
7
|
+
redis_reset
|
8
|
+
ActiveJob::Base.queue_adapter = :test
|
9
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
10
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::RedisSemaphore
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative('./perform_tests')
|
2
|
+
|
3
|
+
class PerformRedlockTest < MiniTest::Test
|
4
|
+
include PerformTests
|
5
|
+
|
6
|
+
def setup
|
7
|
+
redis_reset
|
8
|
+
|
9
|
+
ActiveJob::Base.queue_adapter = :test
|
10
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
11
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::Redlock
|
12
|
+
ActiveJob::Locking.options.hosts = Redlock::Client::DEFAULT_REDIS_URLS
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative('./perform_tests')
|
2
|
+
|
3
|
+
class PerformSuoRedisTest < MiniTest::Test
|
4
|
+
include PerformTests
|
5
|
+
|
6
|
+
def setup
|
7
|
+
redis_reset
|
8
|
+
ActiveJob::Base.queue_adapter = :test
|
9
|
+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
10
|
+
ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::SuoRedis
|
11
|
+
end
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activejob-locking
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Charlie Savage
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activejob
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.0.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.0.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.10.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.10.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redis-semaphore
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redlock
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: suo
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: |
|
84
|
+
activejob-locking lets you control how ActiveJobs are enqueued and performed:
|
85
|
+
|
86
|
+
Allow only one job to be enqueued at a time (based on a lock_id)
|
87
|
+
Allow only one job to be peformed at a time (also based on a lock_id)
|
88
|
+
email:
|
89
|
+
executables: []
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- Gemfile
|
94
|
+
- HISTORY.md
|
95
|
+
- LICENSE
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- lib/activejob-locking.rb
|
99
|
+
- lib/activejob/locking/adapters/base.rb
|
100
|
+
- lib/activejob/locking/adapters/memory.rb
|
101
|
+
- lib/activejob/locking/adapters/redis-semaphore.rb
|
102
|
+
- lib/activejob/locking/adapters/redlock.rb
|
103
|
+
- lib/activejob/locking/adapters/suo-redis.rb
|
104
|
+
- lib/activejob/locking/base.rb
|
105
|
+
- lib/activejob/locking/enqueue.rb
|
106
|
+
- lib/activejob/locking/options.rb
|
107
|
+
- lib/activejob/locking/perform.rb
|
108
|
+
- test/enqueue_tests.rb
|
109
|
+
- test/jobs/enqueue_drop_job.rb
|
110
|
+
- test/jobs/enqueue_wait_job.rb
|
111
|
+
- test/jobs/enqueue_wait_large_timeout_job.rb
|
112
|
+
- test/jobs/enqueue_wait_timeout_job.rb
|
113
|
+
- test/jobs/perform_serially_job.rb
|
114
|
+
- test/jobs/perform_serially_large_timeout_job.rb
|
115
|
+
- test/perform_tests.rb
|
116
|
+
- test/test_enqueue_memory.rb
|
117
|
+
- test/test_enqueue_redis_semaphore.rb
|
118
|
+
- test/test_enqueue_redlock.rb
|
119
|
+
- test/test_enqueue_suo_redis.rb
|
120
|
+
- test/test_helper.rb
|
121
|
+
- test/test_perform_memory.rb
|
122
|
+
- test/test_perform_redis_semaphore.rb
|
123
|
+
- test/test_perform_redlock.rb
|
124
|
+
- test/test_perform_suo_redis.rb
|
125
|
+
homepage: http://github.com/cfis/activejob-locking
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata: {}
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 2.6.8
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: ActiveJob locking to control how jobs are enqueued and performed.
|
149
|
+
test_files: []
|