activejob-locking 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/lantins/activejob-locking.png?branch=master)](http://travis-ci.org/cfis/activejob-locking)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/activejob-locking.png)](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: []
|