activejob-locking 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7cd905211ccba06fbbf2fac424009b84ab5fe769
4
- data.tar.gz: c7c970994a0e5f3ad816c7b5da8ca28722d4596b
3
+ metadata.gz: e574b3a41612fff773ca03309e55b6a2a4bb2db0
4
+ data.tar.gz: 3ef9de73fa5ac528c317fd8218be929ce3e975f8
5
5
  SHA512:
6
- metadata.gz: 1f5f4247c8f15cd41dc0692f1a638a775229404620cee8a6318e4c410dca20e922762145fe5af991c24d71cfacc045ec9dd53f266b4bfa6d9cf22bbd4eed0e6b
7
- data.tar.gz: 7f9a485dac7c34835b7209ed3c7e2736d02ef735a8f6fc6575f8bdb9dcd2831289abe19a9d7c0823960b24a1b6eb6f74b231ac9c7408501abd9437b24d7bc912
6
+ metadata.gz: 28f284c0b260bfdc69387d901f5fb217d1afb512127b05a4a34a8399e0cf36718c33f0c60e857686572f0c4d340a9c83c8889ef3a50510a06114fe961e27b7df
7
+ data.tar.gz: 65c953f75b1795f7be08cd0d262b01584a6f850feb16ac4a7f2432fcedbd964e07820d7ebaa11e3a6c4a043cefbb2bdef315a2529b90069975b0778f13c23ab1
data/HISTORY.md CHANGED
@@ -1,16 +1,21 @@
1
- ## 0.3.0 (2017-06-21)
2
-
3
- - Cleanup handling of hosts
4
-
5
- ## 0.3.0 (2017-06-21)
6
-
7
- - Change lock_key signature to match perform signature
8
-
9
- ## 0.2.0 (2017-06-20)
10
-
11
- - Bug fixes
12
- - Improved tests
13
-
14
- ## 0.1.0 (2017-01-16)
15
-
16
- - Initial release
1
+ ## 0.5.0 (2017-10-03)
2
+
3
+ - Add enqueue_time parameter
4
+ - Fix bug in setting enqueue time
5
+
6
+ ## 0.4.0 (2017-06-21)
7
+
8
+ - Cleanup handling of hosts
9
+
10
+ ## 0.3.0 (2017-06-21)
11
+
12
+ - Change lock_key signature to match perform signature
13
+
14
+ ## 0.2.0 (2017-06-20)
15
+
16
+ - Bug fixes
17
+ - Improved tests
18
+
19
+ ## 0.1.0 (2017-01-16)
20
+
21
+ - Initial release
data/README.md CHANGED
@@ -1,247 +1,274 @@
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 - thus a "unique" job
10
- * Allow only one job to be performed at a time - thus a "serialized" job
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
- Unique Jobs
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 UniqueJob < ActiveJob::Base
34
- include ActiveJob::Locking::Unique
35
-
36
- # Make sure the lock_key is always the same
37
- def lock_key(object)
38
- self.class.name
39
- end
40
-
41
- def perform(object)
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
- Serialized Jobs
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 SerializedJob < ActiveJob::Base
58
- include ActiveJob::Locking::Serialized
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(*args)
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
- The args passed to the lock key method are the same that are passed to the job's perform method.
112
-
113
- To use this gem, you will want to override this method per job.
114
-
115
- ### Examples
116
-
117
- Allow only one job per queue to be enqueued or performed:
118
-
119
- ```ruby
120
- def lock_key(*args)
121
- self.queue
122
- end
123
- ```
124
-
125
- Allow only one instance of a job class to be enqueued of performed:
126
-
127
- ```ruby
128
- def lock_key(*args)
129
- self.class.name
130
- end
131
- ```
132
-
133
- Options
134
- -------
135
- The locking behavior can be dramatically changed by tweaking various options. There is a global set of options
136
- available at:
137
-
138
- ```ruby
139
- ActiveJob::Locking.options
140
- ```
141
- This should be updated using a Rails initializer. Each job class can override individual options as it sees fit.
142
-
143
- ### Adapter
144
-
145
- Use the adapter option to specify which locking gem to use.
146
-
147
- Globally update:
148
-
149
- ```ruby
150
- ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::SuoRedis
151
- ```
152
- Locally update:
153
-
154
- ```ruby
155
- class ExampleJob < ActiveJob::Base
156
- include ActiveJob::Locking::Serialized
157
-
158
- self.adapter = ActiveJob::Locking::Adapters::SuoRedis
159
- end
160
- ```
161
-
162
- ### Hosts
163
-
164
- An array of hosts for the distributed system. This format is dependent on the locking gem, but generally is a url or an existing Memcache or Redis
165
- connection. Please refer to the appropriate locking gem's documentation documentation.
166
-
167
- Globally update:
168
-
169
- ```ruby
170
- ActiveJob::Locking.options.hosts = ['localhost']
171
- ```
172
- Locally update:
173
-
174
- ```ruby
175
- class ExampleJob < ActiveJob::Base
176
- include ActiveJob::Locking::Serialized
177
-
178
- self.hosts = ['localhost']
179
- end
180
- ```
181
-
182
- ### lock_acquire_time
183
-
184
- The is the timeout for acquiring a lock. The value is specified in seconds and defaults to 1. It must
185
- be greater than zero and cannot be nil.
186
-
187
- Globally update:
188
-
189
- ```ruby
190
- ActiveJob::Locking.options.lock_acquire_time = 1
191
- ```
192
- Locally update:
193
-
194
- ```ruby
195
- class ExampleJob < ActiveJob::Base
196
- include ActiveJob::Locking::Unique
197
-
198
- self.lock_acquire_time = 1
199
- end
200
- ```
201
- This greatly influences how enqueuing behavior works. If the timeout is short, then jobs that are waiting to
202
- be enqueued are dropped and the before_enqueue callback will fail. If the timeout is infinite, then jobs will wait
203
- in turn to get enqueued. If the timeout is somewhere in between then it will depend on how long the jobs
204
- take to execute.
205
-
206
- ### lock_time
207
-
208
- The is the time to live for any acquired locks. For most locking gems this is mapped to their concept of "stale" locks.
209
- That means that if an attempt is made to access the lock after it is expired, it will be considered unlocked. That is in
210
- contrast to aggressively removing locks for running jobs even if no other job has requested them.
211
-
212
- The value is specified in seconds and defaults to 100.
213
-
214
- Globally update:
215
-
216
- ```ruby
217
- ActiveJob::Locking.options.lock_time = 100
218
- ```
219
- Locally update:
220
-
221
- ```ruby
222
- class ExampleJob < ActiveJob::Base
223
- include ActiveJob::Locking::Serialized
224
-
225
- self.lock_time = 100
226
- end
227
- ```
228
-
229
- ### AdapterOptions
230
-
231
- This is a hash table of options that should be sent to the lock gem when it is instantiated. Read the lock
232
- gems documentation to find appropriate values.
233
-
234
- Globally update:
235
-
236
- ```ruby
237
- ActiveJob::Locking.options.adapter_options = {}
238
- ```
239
- Locally update (notice the different method name to avoid potential conflicts):
240
-
241
- ```ruby
242
- class ExampleJob < ActiveJob::Base
243
- include ActiveJob::Locking::Unique
244
-
245
- self.adapter_options = {}
246
- end
247
- ```
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 - thus a "unique" job
10
+ * Allow only one job to be performed at a time - thus a "serialized" job
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
+ Unique Jobs
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 UniqueJob < ActiveJob::Base
34
+ include ActiveJob::Locking::Unique
35
+
36
+ # Make sure the lock_key is always the same
37
+ def lock_key(object)
38
+ self.class.name
39
+ end
40
+
41
+ def perform(object)
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
+ Serialized Jobs
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 SerializedJob < ActiveJob::Base
58
+ include ActiveJob::Locking::Serialized
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(*args)
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
+ The args passed to the lock key method are the same that are passed to the job's perform method.
112
+
113
+ To use this gem, you will want to override this method per job.
114
+
115
+ ### Examples
116
+
117
+ Allow only one job per queue to be enqueued or performed:
118
+
119
+ ```ruby
120
+ def lock_key(*args)
121
+ self.queue
122
+ end
123
+ ```
124
+
125
+ Allow only one instance of a job class to be enqueued of performed:
126
+
127
+ ```ruby
128
+ def lock_key(*args)
129
+ self.class.name
130
+ end
131
+ ```
132
+
133
+ Options
134
+ -------
135
+ The locking behavior can be dramatically changed by tweaking various options. There is a global set of options
136
+ available at:
137
+
138
+ ```ruby
139
+ ActiveJob::Locking.options
140
+ ```
141
+ This should be updated using a Rails initializer. Each job class can override individual options as it sees fit.
142
+
143
+ ### Adapter
144
+
145
+ Use the adapter option to specify which locking gem to use.
146
+
147
+ Globally update:
148
+
149
+ ```ruby
150
+ ActiveJob::Locking.options.adapter = ActiveJob::Locking::Adapters::SuoRedis
151
+ ```
152
+ Locally update:
153
+
154
+ ```ruby
155
+ class ExampleJob < ActiveJob::Base
156
+ include ActiveJob::Locking::Serialized
157
+
158
+ self.adapter = ActiveJob::Locking::Adapters::SuoRedis
159
+ end
160
+ ```
161
+
162
+ ### Hosts
163
+
164
+ An array of hosts for the distributed system. This format is dependent on the locking gem, but generally is a url or an existing Memcache or Redis
165
+ connection. Please refer to the appropriate locking gem's documentation documentation.
166
+
167
+ Globally update:
168
+
169
+ ```ruby
170
+ ActiveJob::Locking.options.hosts = ['localhost']
171
+ ```
172
+ Locally update:
173
+
174
+ ```ruby
175
+ class ExampleJob < ActiveJob::Base
176
+ include ActiveJob::Locking::Serialized
177
+
178
+ self.hosts = ['localhost']
179
+ end
180
+ ```
181
+
182
+ ### lock_time
183
+
184
+ The is the time to live for any acquired locks. For most locking gems this is mapped to their concept of "stale" locks.
185
+ That means that if an attempt is made to access the lock after it is expired, it will be considered unlocked. That is in
186
+ contrast to aggressively removing locks for running jobs even if no other job has requested them.
187
+
188
+ The value is specified in seconds and defaults to 100.
189
+
190
+ Globally update:
191
+
192
+ ```ruby
193
+ ActiveJob::Locking.options.lock_time = 100
194
+ ```
195
+ Locally update:
196
+
197
+ ```ruby
198
+ class ExampleJob < ActiveJob::Base
199
+ include ActiveJob::Locking::Serialized
200
+
201
+ self.lock_time = 100
202
+ end
203
+ ```
204
+
205
+ You almost surely want the lock_time to be greater than the time it takes to execute the job. Otherwise, the lock will expire
206
+ and extra jobs will start to run. When the job finishes, or fails, the lock will be released. However, remember that the job
207
+ could be terminated by the operating system or a monitoring system (such as monit). In that case, the lock won't be released
208
+ and will remain in force until its lock_time expires.
209
+
210
+ ### lock_acquire_time
211
+
212
+ The is the timeout for acquiring a lock. The value is specified in seconds and defaults to 1. It must
213
+ be greater than zero and cannot be nil.
214
+
215
+ Globally update:
216
+
217
+ ```ruby
218
+ ActiveJob::Locking.options.lock_acquire_time = 1
219
+ ```
220
+ Locally update:
221
+
222
+ ```ruby
223
+ class ExampleJob < ActiveJob::Base
224
+ include ActiveJob::Locking::Unique
225
+
226
+ self.lock_acquire_time = 1
227
+ end
228
+ ```
229
+
230
+ Remember that most locking gems block the current thread when trying to acquire a lock. Therefore you likely want
231
+ lock_acquire_time to be low. However, the lower it is the more likely that unique jobs that are enqueued will
232
+ expire and be dropped.
233
+
234
+ ### enqueue_time
235
+
236
+ The is the time to re-enqueue a job if the lock_time has expired. Thus this value is only relevant for
237
+ serialized jobs since unique jobs will be dropped instead of enqueded.
238
+
239
+ The value is specified in seconds and defaults to 100.
240
+
241
+ Globally update:
242
+
243
+ ```ruby
244
+ ActiveJob::Locking.options.enqueue_time = 100
245
+ ```
246
+ Locally update:
247
+
248
+ ```ruby
249
+ class ExampleJob < ActiveJob::Base
250
+ include ActiveJob::Locking::Serialized
251
+
252
+ self.enqueue_time = 100
253
+ end
254
+ ```
255
+
256
+ ### AdapterOptions
257
+
258
+ This is a hash table of options that should be sent to the lock gem when it is instantiated. Read the lock
259
+ gems documentation to find appropriate values.
260
+
261
+ Globally update:
262
+
263
+ ```ruby
264
+ ActiveJob::Locking.options.adapter_options = {}
265
+ ```
266
+ Locally update (notice the different method name to avoid potential conflicts):
267
+
268
+ ```ruby
269
+ class ExampleJob < ActiveJob::Base
270
+ include ActiveJob::Locking::Unique
271
+
272
+ self.adapter_options = {}
273
+ end
274
+ ```
@@ -1,23 +1,24 @@
1
- require 'activejob/locking/adapters/base'
2
- require 'activejob/locking/adapters/memory'
3
-
4
- require 'activejob/locking/base'
5
- require 'activejob/locking/unique'
6
- require 'activejob/locking/serialized'
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
- lock_time: 100,
15
- lock_acquire_time: 1,
16
- adapter_options: {})
17
-
18
- def self.options
19
- @options
20
- end
21
- end
22
- end
23
-
1
+ require 'activejob/locking/adapters/base'
2
+ require 'activejob/locking/adapters/memory'
3
+
4
+ require 'activejob/locking/base'
5
+ require 'activejob/locking/unique'
6
+ require 'activejob/locking/serialized'
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
+ enqueue_time: 100, # seconds
15
+ lock_time: 100, # seconds
16
+ lock_acquire_time: 1, # seconds
17
+ adapter_options: {})
18
+
19
+ def self.options
20
+ @options
21
+ end
22
+ end
23
+ end
24
+
@@ -1,32 +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
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
32
  end
@@ -1,58 +1,58 @@
1
- module ActiveJob
2
- module Locking
3
- module Adapters
4
- class Memory < Base
5
- @hash = Hash.new
6
- @mutex = Mutex.new
7
-
8
- def self.lock(key)
9
- @mutex.synchronize do
10
- if @hash[key]
11
- false
12
- else
13
- @hash[key] = Time.now
14
- end
15
- end
16
- end
17
-
18
- def self.unlock(key)
19
- @mutex.synchronize do
20
- @hash.delete(key)
21
- end
22
- end
23
-
24
- def self.locked?(key)
25
- @mutex.synchronize do
26
- @hash.include?(key)
27
- end
28
- end
29
-
30
- def self.reset
31
- @mutex.synchronize do
32
- @hash = Hash.new
33
- end
34
- end
35
-
36
- def create_lock_manager
37
- end
38
-
39
- def lock
40
- finish = Time.now + self.options.lock_acquire_time
41
- sleep_time = [5, self.options.lock_acquire_time / 5].min
42
-
43
- begin
44
- lock = self.class.lock(key)
45
- return lock if lock
46
- sleep(sleep_time)
47
- end while Time.now < finish
48
-
49
- false
50
- end
51
-
52
- def unlock
53
- self.class.unlock(self.key)
54
- end
55
- end
56
- end
57
- end
1
+ module ActiveJob
2
+ module Locking
3
+ module Adapters
4
+ class Memory < Base
5
+ @hash = Hash.new
6
+ @mutex = Mutex.new
7
+
8
+ def self.lock(key)
9
+ @mutex.synchronize do
10
+ if @hash[key]
11
+ false
12
+ else
13
+ @hash[key] = Time.now
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.unlock(key)
19
+ @mutex.synchronize do
20
+ @hash.delete(key)
21
+ end
22
+ end
23
+
24
+ def self.locked?(key)
25
+ @mutex.synchronize do
26
+ @hash.include?(key)
27
+ end
28
+ end
29
+
30
+ def self.reset
31
+ @mutex.synchronize do
32
+ @hash = Hash.new
33
+ end
34
+ end
35
+
36
+ def create_lock_manager
37
+ end
38
+
39
+ def lock
40
+ finish = Time.now + self.options.lock_acquire_time
41
+ sleep_time = [5, self.options.lock_acquire_time / 5].min
42
+
43
+ begin
44
+ lock = self.class.lock(key)
45
+ return lock if lock
46
+ sleep(sleep_time)
47
+ end while Time.now < finish
48
+
49
+ false
50
+ end
51
+
52
+ def unlock
53
+ self.class.unlock(self.key)
54
+ end
55
+ end
56
+ end
57
+ end
58
58
  end
@@ -1,26 +1,26 @@
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.first,
9
- resources: 1,
10
- stale_client_timeout: self.options.lock_time}.merge(self.options.adapter_options)
11
-
12
- Redis::Semaphore.new(self.key, mapped_options)
13
- end
14
-
15
- def lock
16
- self.lock_token = self.lock_manager.lock(self.options.lock_acquire_time)
17
- end
18
-
19
- def unlock
20
- self.lock_manager.signal(self.lock_token)
21
- self.lock_token = nil
22
- end
23
- end
24
- end
25
- end
26
- end
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.first,
9
+ resources: 1,
10
+ stale_client_timeout: self.options.lock_time}.merge(self.options.adapter_options)
11
+
12
+ Redis::Semaphore.new(self.key, mapped_options)
13
+ end
14
+
15
+ def lock
16
+ self.lock_token = self.lock_manager.lock(self.options.lock_acquire_time)
17
+ end
18
+
19
+ def unlock
20
+ self.lock_manager.signal(self.lock_token)
21
+ self.lock_token = nil
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,26 +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] = 2 # Try to get the lock and then try again when timeout is expiring--
10
- mapped_options[:retry_delay] = self.options.lock_acquire_time * 1000 # convert from seconds to milliseconds
11
-
12
- ::Redlock::Client.new(self.options.hosts, mapped_options)
13
- end
14
-
15
- def lock
16
- self.lock_token = self.lock_manager.lock(self.key, self.options.lock_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
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] = 2 # Try to get the lock and then try again when timeout is expiring--
10
+ mapped_options[:retry_delay] = self.options.lock_acquire_time * 1000 # convert from seconds to milliseconds
11
+
12
+ ::Redlock::Client.new(self.options.hosts, mapped_options)
13
+ end
14
+
15
+ def lock
16
+ self.lock_token = self.lock_manager.lock(self.key, self.options.lock_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
@@ -1,25 +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.first},
9
- stale_lock_expiration: self.options.lock_time,
10
- acquisition_timeout: self.options.lock_acquire_time}
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
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.first},
9
+ stale_lock_expiration: self.options.lock_time,
10
+ acquisition_timeout: self.options.lock_acquire_time}
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
@@ -7,8 +7,8 @@ module ActiveJob
7
7
  def lock_options
8
8
  @lock_options ||= ActiveJob::Locking::Options.new
9
9
  end
10
- delegate :adapter, :hosts, :lock_time, :lock_acquire_time, :adapter_options, to: :lock_options
11
- delegate :adapter=, :hosts=, :lock_time=, :lock_acquire_time=, :adapter_options=, to: :lock_options
10
+ delegate :adapter, :enqueue_time, :hosts, :lock_time, :lock_acquire_time, :adapter_options, to: :lock_options
11
+ delegate :adapter=, :enqueue_time=, :hosts=, :lock_time=, :lock_acquire_time=, :adapter_options=, to: :lock_options
12
12
  end
13
13
 
14
14
  included do
@@ -7,16 +7,31 @@ module ActiveJob
7
7
  attr_accessor :hosts
8
8
  attr_accessor :lock_time
9
9
  attr_accessor :lock_acquire_time
10
+ attr_accessor :enqueue_time
10
11
  attr_accessor :adapter_options
11
12
 
12
13
  def initialize(options = {})
13
14
  @adapter = options[:adapter]
14
15
  @hosts = options[:hosts]
16
+ @enqueue_time = options[:enqueue_time]
15
17
  @lock_time = options[:lock_time]
16
18
  @lock_acquire_time = options[:lock_acquire_time]
17
19
  @adapter_options = options[:adapter_options]
18
20
  end
19
21
 
22
+ def enqueue_time=(value)
23
+ case value
24
+ when NilClass
25
+ raise(ArgumentError, 'Enqueue time must be set')
26
+ when ActiveSupport::Duration
27
+ @enqueue_time = value.value
28
+ when 0
29
+ raise(ArgumentError, 'Enqueue time must be greater than zero')
30
+ else
31
+ @enqueue_time = value
32
+ end
33
+ end
34
+
20
35
  def lock_time=(value)
21
36
  case value
22
37
  when NilClass
@@ -47,6 +62,7 @@ module ActiveJob
47
62
  result = self.dup
48
63
  result.adapter = other.adapter if other.adapter
49
64
  result.hosts = other.hosts if other.hosts
65
+ result.enqueue_time = other.enqueue_time if other.enqueue_time
50
66
  result.lock_time = other.lock_time if other.lock_time
51
67
  result.lock_acquire_time = other.lock_acquire_time if other.lock_acquire_time
52
68
  result.adapter_options = other.adapter_options if other.adapter_options
@@ -14,7 +14,7 @@ module ActiveJob
14
14
  job.adapter.unlock
15
15
  end
16
16
  else
17
- job.class.set(wait: job.class.lock_acquire_time).perform_later(*job.arguments)
17
+ job.class.set(wait: job.adapter.options.enqueue_time).perform_later(*job.arguments)
18
18
  end
19
19
  end
20
20
  end
@@ -1,15 +1,15 @@
1
- class FailJob < ActiveJob::Base
2
- include ActiveJob::Locking::Unique
3
-
4
- self.lock_acquire_time = 2
5
-
6
- # We want the job ids to be all the same for testing
7
- def lock_key(index, sleep_time)
8
- self.class.name
9
- end
10
-
11
- # Pass in index so we can distinguish different jobs
12
- def perform(index, sleep_time)
13
- raise(ArgumentError, 'Job failed')
14
- end
1
+ class FailJob < ActiveJob::Base
2
+ include ActiveJob::Locking::Unique
3
+
4
+ self.lock_acquire_time = 2
5
+
6
+ # We want the job ids to be all the same for testing
7
+ def lock_key(index, sleep_time)
8
+ self.class.name
9
+ end
10
+
11
+ # Pass in index so we can distinguish different jobs
12
+ def perform(index, sleep_time)
13
+ raise(ArgumentError, 'Job failed')
14
+ end
15
15
  end
@@ -1,15 +1,15 @@
1
- class SerialJob < ActiveJob::Base
2
- include ActiveJob::Locking::Serialized
3
-
4
- self.lock_acquire_time = 2
5
-
6
- # We want the job ids to be all the same for testing
7
- def lock_key(index, sleep_time)
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
1
+ class SerialJob < ActiveJob::Base
2
+ include ActiveJob::Locking::Serialized
3
+
4
+ self.lock_acquire_time = 2
5
+
6
+ # We want the job ids to be all the same for testing
7
+ def lock_key(index, sleep_time)
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
15
  end
@@ -1,15 +1,15 @@
1
- class UniqueJob < ActiveJob::Base
2
- include ActiveJob::Locking::Unique
3
-
4
- self.lock_acquire_time = 2
5
-
6
- # We want the job ids to be all the same for testing
7
- def lock_key(index, sleep_time)
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
1
+ class UniqueJob < ActiveJob::Base
2
+ include ActiveJob::Locking::Unique
3
+
4
+ self.lock_acquire_time = 2
5
+
6
+ # We want the job ids to be all the same for testing
7
+ def lock_key(index, sleep_time)
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
15
  end
data/test/test_suite.rb CHANGED
@@ -1,12 +1,12 @@
1
- %w(
2
- test_serialized_memory
3
- test_serialized_redis_semaphore
4
- test_serialized_redlock
5
- test_serialized_suo_redis
6
- test_unique_memory
7
- test_unique_redis_semaphore
8
- test_unique_redlock
9
- test_unique_suo_redis
10
- ).each do |test|
11
- require File.expand_path("../#{test}", __FILE__)
12
- end
1
+ %w(
2
+ test_serialized_memory
3
+ test_serialized_redis_semaphore
4
+ test_serialized_redlock
5
+ test_serialized_suo_redis
6
+ test_unique_memory
7
+ test_unique_redis_semaphore
8
+ test_unique_redlock
9
+ test_unique_suo_redis
10
+ ).each do |test|
11
+ require File.expand_path("../#{test}", __FILE__)
12
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activejob-locking
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charlie Savage
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-23 00:00:00.000000000 Z
11
+ date: 2017-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -154,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
154
  version: '0'
155
155
  requirements: []
156
156
  rubyforge_project:
157
- rubygems_version: 2.6.11
157
+ rubygems_version: 2.6.13
158
158
  signing_key:
159
159
  specification_version: 4
160
160
  summary: ActiveJob locking to control how jobs are enqueued and performed.