active_record_mutex 3.1.0 → 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +14 -0
- data/active_record_mutex.gemspec +2 -2
- data/lib/active_record/database_mutex/implementation.rb +58 -40
- data/lib/active_record/database_mutex/version.rb +1 -1
- data/lib/active_record/database_mutex.rb +1 -1
- data/test/database_mutex_test.rb +23 -6
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36b8a40a6140ecdfcd0e3892d2e008d502acdce96826ef3aecc797cadb65fa89
|
4
|
+
data.tar.gz: 38bf80d5ed330479f5a8399633048c606f7d03b71aaccacf512509c9cdcbb58e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a580eda2be88864af2b8aec078b09b5757992063f9078e64afb195c167bfc6b858f1d0674f2c348e66659c9f759e2cb47bb147a9b6118e9817510cdf6b77cb05
|
7
|
+
data.tar.gz: 51e00870718236b49c3271a0164d68fe6708e5b2451daad87b9f211090b3d7979a06546665d25082dadaca028fbb773c544ac33f7932c3a8d04150c2e31ff369
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 2024-10-16 v3.2.1
|
4
|
+
|
5
|
+
* Refactor DatabaseMutex implementation:
|
6
|
+
* Change `@name` and `mutex.name` to downcase and freeze values
|
7
|
+
* Update tests to reflect new behavior
|
8
|
+
|
9
|
+
## 2024-10-16 v3.2.0
|
10
|
+
|
11
|
+
* DatabaseMutex improvements:
|
12
|
+
* Improved lock name generation in `lock_name` method.
|
13
|
+
* Modified SQL queries to use `lock_name` instead of `internal_name`.
|
14
|
+
* Added `lock_exists?` and `test_all_mutexes` methods for testing mutex
|
15
|
+
creation and querying.
|
16
|
+
|
3
17
|
## 2024-10-16 v3.1.0
|
4
18
|
|
5
19
|
* Changes for **3.1.0**:
|
data/active_record_mutex.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: active_record_mutex 3.1
|
2
|
+
# stub: active_record_mutex 3.2.1 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "active_record_mutex".freeze
|
6
|
-
s.version = "3.1
|
6
|
+
s.version = "3.2.1".freeze
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib".freeze]
|
@@ -20,8 +20,10 @@ module ActiveRecord
|
|
20
20
|
#
|
21
21
|
# @raise [ ArgumentError ] if no **name** option is provided in the options hash.
|
22
22
|
def initialize(opts = {})
|
23
|
-
@name = opts[:name]
|
24
|
-
|
23
|
+
@name = opts[:name].to_s.downcase
|
24
|
+
@name.size != 0 or raise ArgumentError, "mutex requires a nonempty :name argument"
|
25
|
+
@name.freeze
|
26
|
+
lock_name # create/check internal_name and lock_name
|
25
27
|
end
|
26
28
|
|
27
29
|
# Returns the name of this mutex as given via the constructor argument.
|
@@ -34,7 +36,8 @@ module ActiveRecord
|
|
34
36
|
def internal_name
|
35
37
|
@internal_name and return @internal_name
|
36
38
|
encoded_name = ?$ + Digest::MD5.base64digest([ self.class.name, name ] * ?#).
|
37
|
-
delete('^A-Za-z0-9+/').gsub(/[+\/]/, ?+ => ?_, ?/ => ?.)
|
39
|
+
delete('^A-Za-z0-9+/').gsub(/[+\/]/, ?+ => ?_, ?/ => ?.).
|
40
|
+
downcase.freeze
|
38
41
|
if encoded_name.size <= 64
|
39
42
|
@internal_name = encoded_name
|
40
43
|
else
|
@@ -43,38 +46,51 @@ module ActiveRecord
|
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
49
|
+
# The lock_name method generates the name for the mutex's internal lock
|
50
|
+
# variable based on its class and {name} attributes, prefixing it with a
|
51
|
+
# truncated version of the name that only includes printable characters.
|
52
|
+
#
|
53
|
+
# @return [ String ] the generated lock name
|
54
|
+
def lock_name
|
55
|
+
@lock_name and return @lock_name
|
56
|
+
prefix_name = name.gsub(/[^[:print:]]/, '')[0, 32]
|
57
|
+
@lock_name = prefix_name + ?= + internal_name
|
58
|
+
@lock_name.downcase!
|
59
|
+
@lock_name.freeze
|
60
|
+
end
|
61
|
+
|
62
|
+
# The synchronize method attempts to acquire a mutex lock for the given name
|
63
|
+
# and executes the block passed to it. If the lock is already held by another
|
64
|
+
# database connection, this method will return nil instead of raising an
|
65
|
+
# exception and not execute the block. #
|
66
|
+
#
|
67
|
+
# This method provides a convenient way to ensure that critical sections of code
|
68
|
+
# are executed while holding the mutex lock. It attempts to acquire the lock using
|
69
|
+
# the underlying locking mechanisms (such as {lock} and {unlock}) and executes
|
70
|
+
# the block passed to it.
|
71
|
+
#
|
72
|
+
# The **block** and **timeout** options are passed to the {lock} method
|
73
|
+
# and configure the way the lock is acquired.
|
74
|
+
#
|
75
|
+
# The **force** option is passed to the {unlock} method, which will force the
|
76
|
+
# lock to open if true.
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# foo.mutex.synchronize { do_something_with foo } # wait forever and never give up
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# foo.mutex.synchronize(timeout: 5) { do_something_with foo } # wait 5s and give up
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# unless foo.mutex.synchronize(block: false) { do_something_with foo }
|
86
|
+
# # try again later
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# @param opts [ Hash ] Options hash containing the **block**, **timeout**, or **force** keys
|
90
|
+
#
|
91
|
+
# @yield [ Result ] The block to be executed while holding the mutex lock
|
92
|
+
#
|
93
|
+
# @return [ Nil or result of yielded block ] depending on whether the lock was acquired
|
78
94
|
def synchronize(opts = {})
|
79
95
|
locked = lock(opts.slice(:block, :timeout)) or return
|
80
96
|
yield
|
@@ -147,7 +163,7 @@ module ActiveRecord
|
|
147
163
|
decrement_counter
|
148
164
|
end
|
149
165
|
if counter_zero?
|
150
|
-
case query("SELECT RELEASE_LOCK(#{quote(
|
166
|
+
case query("SELECT RELEASE_LOCK(#{quote(lock_name)})")
|
151
167
|
when 1
|
152
168
|
true
|
153
169
|
when 0, nil
|
@@ -181,7 +197,7 @@ module ActiveRecord
|
|
181
197
|
#
|
182
198
|
# @return [ true, false ] true if the mutex is unlocked, false otherwise
|
183
199
|
def unlocked?
|
184
|
-
query("SELECT IS_FREE_LOCK(#{quote(
|
200
|
+
query("SELECT IS_FREE_LOCK(#{quote(lock_name)})") == 1
|
185
201
|
end
|
186
202
|
|
187
203
|
# The locked? method returns true if this mutex is currently locked by
|
@@ -194,7 +210,7 @@ module ActiveRecord
|
|
194
210
|
|
195
211
|
# Returns true if the mutex is was acquired on this database connection.
|
196
212
|
def owned?
|
197
|
-
query("SELECT CONNECTION_ID() = IS_USED_LOCK(#{quote(
|
213
|
+
query("SELECT CONNECTION_ID() = IS_USED_LOCK(#{quote(lock_name)})") == 1
|
198
214
|
end
|
199
215
|
|
200
216
|
# Returns true if this mutex was not acquired on this database connection,
|
@@ -225,13 +241,15 @@ module ActiveRecord
|
|
225
241
|
ActiveRecord::Base.connection.quote(value)
|
226
242
|
end
|
227
243
|
|
244
|
+
alias counter_name internal_name
|
245
|
+
|
228
246
|
# The counter method generates a unique name for the mutex's internal
|
229
247
|
# counter variable. This name is used as part of the SQL query to set and
|
230
248
|
# retrieve the counter value.
|
231
249
|
#
|
232
250
|
# @return [String] the unique name for the mutex's internal counter variable.
|
233
251
|
def counter
|
234
|
-
"@#{
|
252
|
+
"@#{counter_name}"
|
235
253
|
end
|
236
254
|
|
237
255
|
# The increment_counter method increments the internal counter value for
|
@@ -290,7 +308,7 @@ module ActiveRecord
|
|
290
308
|
increment_counter
|
291
309
|
true
|
292
310
|
else
|
293
|
-
case query("SELECT GET_LOCK(#{quote(
|
311
|
+
case query("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})")
|
294
312
|
when 1
|
295
313
|
increment_counter
|
296
314
|
true
|
data/test/database_mutex_test.rb
CHANGED
@@ -25,7 +25,7 @@ class DatabaseMutexTest < Test::Unit::TestCase
|
|
25
25
|
old, ENV['RAILS_ENV'] = ENV['RAILS_ENV'], nil
|
26
26
|
mutex = Foo.mutex
|
27
27
|
assert_kind_of ActiveRecord::DatabaseMutex::Implementation, mutex
|
28
|
-
assert_equal Foo.name, mutex.name
|
28
|
+
assert_equal Foo.name.downcase, mutex.name
|
29
29
|
ensure
|
30
30
|
ENV['RAILS_ENV'] = old
|
31
31
|
end
|
@@ -34,7 +34,7 @@ class DatabaseMutexTest < Test::Unit::TestCase
|
|
34
34
|
old, ENV['RAILS_ENV'] = ENV['RAILS_ENV'], 'test'
|
35
35
|
mutex = Foo.mutex
|
36
36
|
assert_kind_of ActiveRecord::DatabaseMutex::Implementation, mutex
|
37
|
-
assert_equal "#{Foo.name}@test", mutex.name
|
37
|
+
assert_equal "#{Foo.name}@test".downcase, mutex.name
|
38
38
|
ensure
|
39
39
|
ENV['RAILS_ENV'] = old
|
40
40
|
end
|
@@ -47,7 +47,7 @@ class DatabaseMutexTest < Test::Unit::TestCase
|
|
47
47
|
assert_equal true, instance.save
|
48
48
|
mutex = instance.mutex
|
49
49
|
assert_kind_of ActiveRecord::DatabaseMutex::Implementation, mutex
|
50
|
-
assert_equal "#{instance.id}@#{Foo.name}", mutex.name
|
50
|
+
assert_equal "#{instance.id}@#{Foo.name}".downcase, mutex.name
|
51
51
|
end
|
52
52
|
|
53
53
|
def test_factory_method_for
|
@@ -55,9 +55,26 @@ class DatabaseMutexTest < Test::Unit::TestCase
|
|
55
55
|
assert_kind_of ActiveRecord::DatabaseMutex::Implementation, mutex
|
56
56
|
end
|
57
57
|
|
58
|
+
private def lock_exists?(string)
|
59
|
+
string = string.downcase
|
60
|
+
ActiveRecord::Base.all_mutexes.map(&:OBJECT_NAME).any? {
|
61
|
+
_1.include?(string)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_all_mutexes
|
66
|
+
string = SecureRandom.hex(16)
|
67
|
+
mutex = ActiveRecord::DatabaseMutex.for(string)
|
68
|
+
assert_false lock_exists?(string)
|
69
|
+
mutex.lock
|
70
|
+
assert lock_exists?(string)
|
71
|
+
ensure
|
72
|
+
mutex.unlock force: true
|
73
|
+
end
|
74
|
+
|
58
75
|
def test_create
|
59
76
|
mutex = Implementation.new(:name => 'Create')
|
60
|
-
assert_equal '
|
77
|
+
assert_equal 'create', mutex.name
|
61
78
|
end
|
62
79
|
|
63
80
|
def test_lock
|
@@ -268,11 +285,11 @@ class DatabaseMutexTest < Test::Unit::TestCase
|
|
268
285
|
|
269
286
|
def test_internal_name
|
270
287
|
mutex = Implementation.new(:name => (250..255).map(&:chr) * '')
|
271
|
-
assert_equal '$
|
288
|
+
assert_equal '$3i3xqvurnpgyh6kaiokipw', mutex.send(:internal_name)
|
272
289
|
end
|
273
290
|
|
274
291
|
def test_counter_name
|
275
292
|
mutex = Implementation.new(:name => (250..255).map(&:chr) * '')
|
276
|
-
assert_equal '@$
|
293
|
+
assert_equal '@$3i3xqvurnpgyh6kaiokipw', mutex.send(:counter)
|
277
294
|
end
|
278
295
|
end
|