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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 337ab2cfb637dc992dda05b67bc12098b0d8d7266591dc741d4918dbb3238aee
4
- data.tar.gz: e812c787de8cb82696027803eafa426793af334d4c1d847c9956473a7d1faf20
3
+ metadata.gz: 36b8a40a6140ecdfcd0e3892d2e008d502acdce96826ef3aecc797cadb65fa89
4
+ data.tar.gz: 38bf80d5ed330479f5a8399633048c606f7d03b71aaccacf512509c9cdcbb58e
5
5
  SHA512:
6
- metadata.gz: f8cb37ec99b0a52267f964b032b8f061801bef3000a11d355af5cddef3d5f075c471bd51e3fa79310136cbc53b3cd0376c293a501363b4f48f979fc774f03efc
7
- data.tar.gz: 1418b58e067e151bb33e92cdd7beb215cb3fdce4ea9f7439e34ae6315b452c80d9e0cafa5f368f399ec2abb7d48c0479a1a233b1fc01f4ff364043042647faa1
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**:
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: active_record_mutex 3.1.0 ruby lib
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.0".freeze
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] or raise ArgumentError, "mutex requires a :name argument"
24
- internal_name # create/check internal_name
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
- # The synchronize method attempts to acquire a mutex lock for the given name
47
- # and executes the block passed to it. If the lock is already held by another
48
- # database connection, this method will return nil instead of raising an
49
- # exception and not execute the block. #
50
- #
51
- # This method provides a convenient way to ensure that critical sections of code
52
- # are executed while holding the mutex lock. It attempts to acquire the lock using
53
- # the underlying locking mechanisms (such as {lock} and {unlock}) and executes
54
- # the block passed to it.
55
- #
56
- # The **block** and **timeout** options are passed to the {lock} method
57
- # and configure the way the lock is acquired.
58
- #
59
- # The **force** option is passed to the {unlock} method, which will force the
60
- # lock to open if true.
61
- #
62
- # @example
63
- # foo.mutex.synchronize { do_something_with foo } # wait forever and never give up
64
- #
65
- # @example
66
- # foo.mutex.synchronize(timeout: 5) { do_something_with foo } # wait 5s and give up
67
- #
68
- # @example
69
- # unless foo.mutex.synchronize(block: false) { do_something_with foo }
70
- # # try again later
71
- # end
72
- #
73
- # @param opts [ Hash ] Options hash containing the **block**, **timeout**, or **force** keys
74
- #
75
- # @yield [ Result ] The block to be executed while holding the mutex lock
76
- #
77
- # @return [ Nil or result of yielded block ] depending on whether the lock was acquired
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(internal_name)})")
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(internal_name)})") == 1
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(internal_name)})") == 1
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
- "@#{internal_name}"
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(internal_name)}, #{timeout})")
311
+ case query("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})")
294
312
  when 1
295
313
  increment_counter
296
314
  true
@@ -1,6 +1,6 @@
1
1
  module ActiveRecord::DatabaseMutex
2
2
  # ActiveRecord::DatabaseMutex version
3
- VERSION = '3.1.0'
3
+ VERSION = '3.2.1'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -76,7 +76,7 @@ module ActiveRecord
76
76
  connection.select_all(<<~EOT).map { MutexInfo.new(_1) }
77
77
  SELECT * FROM performance_schema.metadata_locks
78
78
  WHERE OBJECT_TYPE = 'USER LEVEL LOCK'
79
- AND OBJECT_NAME LIKE "$%"
79
+ AND OBJECT_NAME LIKE "%=$%"
80
80
  EOT
81
81
  end
82
82
  end
@@ -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 'Create', mutex.name
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 '$3i3xQvUrNPGyH6kaIOkiPw', mutex.send(:internal_name)
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 '@$3i3xQvUrNPGyH6kaIOkiPw', mutex.send(:counter)
293
+ assert_equal '@$3i3xqvurnpgyh6kaiokipw', mutex.send(:counter)
277
294
  end
278
295
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_mutex
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank