active_record_mutex 3.1.0 → 3.2.1
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 +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
|