with_advisory_lock 7.0.0 → 7.0.2

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: 2af1c67f9aa840ba30d4c1a280a74a02bd46e83cef6ee426a59e16d77afaf99f
4
- data.tar.gz: 055c8a8f251605c01d91fb4ead5f5cd3ab31b4327ee1bdb2b5fbd7b24cd0e455
3
+ metadata.gz: 7a82d92e5b44f2cf65a9af5ac2d00baacab6d47b3b47be9fc2db82bd5677e4c7
4
+ data.tar.gz: 4a9b77cd24fad228812b9eff2f65079e6be7b95130a8e8d628d71525045ef902
5
5
  SHA512:
6
- metadata.gz: 9ea47f0a89759ee4573b7c0bf7a481e48bf7e5e6feac6b132ee42a5441478bb0d02840f1e40d5963ebc5c5bc93af47a21e4f58337e33c42091d07160994a0821
7
- data.tar.gz: b584c732c8fd97dd74cc6c92286d19ef9f79fbb9911a626c092c0197028cc99540f3af5dce34fbc9579fd1c01d207c7426e57b91c0928cbb4ff3620f62bd0995
6
+ metadata.gz: 77996aa445351903666828d916b8bd02ad4357496c2240ac847f937f7b3adff7af83993b10d6af8bcdb33825992dc6391cc211f97428c36b2bd102e65e5c26b2
7
+ data.tar.gz: 94ec70a26f7c336f9dba44a384bc16d9d680d7495e43cb0c9fc22fee2380317f5d5263f36d3998837ca21f3e05424c7fc0fdb37ed75daf441ce534f0b4d67339
@@ -1 +1 @@
1
- {".":"7.0.0"}
1
+ {".":"7.0.2"}
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.4.4
1
+ ruby 3.4.6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## Changelog
2
2
 
3
+ ## [7.0.2](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v7.0.1...with_advisory_lock/v7.0.2) (2025-09-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * Replace connection.select_value with connection.query_value ([#131](https://github.com/ClosureTree/with_advisory_lock/issues/131)) ([dc01977](https://github.com/ClosureTree/with_advisory_lock/commit/dc01977e5e3a120843b19a5e6befd538c6e36516))
9
+
10
+ ## [7.0.1](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v7.0.0...with_advisory_lock/v7.0.1) (2025-07-21)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * handle ActiveRecord's release_advisory_lock signature for Rails 7.2+ ([#127](https://github.com/ClosureTree/with_advisory_lock/issues/127)) ([94253ca](https://github.com/ClosureTree/with_advisory_lock/commit/94253ca2af7f684a3c99645765853546c3da8e02)), closes [#126](https://github.com/ClosureTree/with_advisory_lock/issues/126)
16
+
3
17
  ## [7.0.0](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v6.0.0...with_advisory_lock/v7.0.0) (2025-07-05)
4
18
 
5
19
 
@@ -25,8 +25,17 @@ module WithAdvisoryLock
25
25
  execute_successful?("GET_LOCK(#{quote(lock_keys.first)}, #{mysql_timeout})")
26
26
  end
27
27
 
28
- def release_advisory_lock(lock_keys, lock_name:, **)
29
- execute_successful?("RELEASE_LOCK(#{quote(lock_keys.first)})")
28
+ def release_advisory_lock(*args, **kwargs)
29
+ # Handle both signatures - ActiveRecord's built-in and ours
30
+ if args.length == 1 && kwargs.empty?
31
+ # ActiveRecord's signature: release_advisory_lock(lock_id)
32
+ # Called by Rails migrations with a single positional argument
33
+ super
34
+ else
35
+ # Our signature: release_advisory_lock(lock_keys, lock_name:, **)
36
+ lock_keys = args.first
37
+ execute_successful?("RELEASE_LOCK(#{quote(lock_keys.first)})")
38
+ end
30
39
  rescue ActiveRecord::StatementInvalid => e
31
40
  # If the connection is broken, the lock is automatically released by MySQL
32
41
  # No need to fail the release operation
@@ -56,7 +65,7 @@ module WithAdvisoryLock
56
65
  private
57
66
 
58
67
  def execute_successful?(mysql_function)
59
- select_value("SELECT #{mysql_function}") == 1
68
+ query_value("SELECT #{mysql_function}") == 1
60
69
  end
61
70
  end
62
71
  end
@@ -71,7 +71,7 @@ module WithAdvisoryLock
71
71
  LIMIT 1
72
72
  SQL
73
73
 
74
- select_value(query).present?
74
+ query_value(query).present?
75
75
  rescue ActiveRecord::StatementInvalid
76
76
  # If pg_locks is not accessible, fall back to nil to indicate we should use the default method
77
77
  nil
@@ -96,7 +96,7 @@ module WithAdvisoryLock
96
96
  end
97
97
 
98
98
  def execute_advisory(function, lock_keys, lock_name)
99
- result = select_value(prepare_sql(function, lock_keys, lock_name))
99
+ result = query_value(prepare_sql(function, lock_keys, lock_name))
100
100
  LOCK_RESULT_VALUES.include?(result)
101
101
  end
102
102
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WithAdvisoryLock
4
- VERSION = Gem::Version.new('7.0.0')
4
+ VERSION = Gem::Version.new('7.0.2')
5
5
  end
@@ -133,6 +133,25 @@ module LockTestCases
133
133
  # After the block, current_advisory_lock should be nil regardless
134
134
  assert_nil model_class.current_advisory_lock
135
135
  end
136
+
137
+ test 'does not write and read lock queries from the query cache' do
138
+ model_class.connection.cache do
139
+ model_class.with_advisory_lock!("lock", timeout_seconds: 0) {}
140
+ assert_nil model_class.connection.query_cache["SELECT GET_LOCK('lock', 0)"]
141
+
142
+ # Blocker thread uses a different connection
143
+ blocker = Thread.new do
144
+ model_class.with_advisory_lock("lock", timeout_seconds: 0) { sleep 1 }
145
+ end
146
+ # blocker should get the lock and not release it in time
147
+ sleep 0.5
148
+ lock_result = model_class.with_advisory_lock_result("lock", timeout_seconds: 0) do
149
+ raise "Successfully entered critical region while lock was held by other thread"
150
+ end
151
+ blocker.join
152
+ assert_not(lock_result.lock_was_acquired?)
153
+ end
154
+ end
136
155
  end
137
156
  end
138
157
 
@@ -177,7 +196,7 @@ class MySQLLockTest < GemTestCase
177
196
  # Hold a lock in another connection - need to use the same prefixed name as the gem
178
197
  other_conn = model_class.connection_pool.checkout
179
198
  lock_keys = other_conn.lock_keys_for(lock_name)
180
- other_conn.select_value("SELECT GET_LOCK(#{other_conn.quote(lock_keys.first)}, 0)")
199
+ other_conn.query_value("SELECT GET_LOCK(#{other_conn.quote(lock_keys.first)}, 0)")
181
200
 
182
201
  begin
183
202
  # Attempt to acquire with a short timeout - should fail quickly
@@ -190,7 +209,7 @@ class MySQLLockTest < GemTestCase
190
209
  assert_not result
191
210
  assert elapsed < 3.0, "Expected quick timeout, but took #{elapsed} seconds"
192
211
  ensure
193
- other_conn.select_value("SELECT RELEASE_LOCK(#{other_conn.quote(lock_keys.first)})")
212
+ other_conn.query_value("SELECT RELEASE_LOCK(#{other_conn.quote(lock_keys.first)})")
194
213
  model_class.connection_pool.checkin(other_conn)
195
214
  end
196
215
  end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class MySQLReleaseLockTest < GemTestCase
6
+ self.use_transactional_tests = false
7
+
8
+ def model_class
9
+ MysqlTag
10
+ end
11
+
12
+ def setup
13
+ super
14
+ begin
15
+ skip unless model_class.connection.adapter_name =~ /mysql/i
16
+ MysqlTag.delete_all
17
+ rescue ActiveRecord::NoDatabaseError
18
+ skip "MySQL database not available. Please create the database first."
19
+ rescue StandardError => e
20
+ skip "MySQL connection failed: #{e.message}"
21
+ end
22
+ end
23
+
24
+ test 'release_advisory_lock handles gem signature with lock_keys' do
25
+ lock_name = 'test_gem_signature'
26
+ lock_keys = model_class.connection.lock_keys_for(lock_name)
27
+
28
+ # Acquire the lock
29
+ result = model_class.connection.try_advisory_lock(
30
+ lock_keys,
31
+ lock_name: lock_name,
32
+ shared: false,
33
+ transaction: false
34
+ )
35
+ assert result, 'Failed to acquire lock'
36
+
37
+ # Release using gem signature
38
+ released = model_class.connection.release_advisory_lock(
39
+ lock_keys,
40
+ lock_name: lock_name,
41
+ shared: false,
42
+ transaction: false
43
+ )
44
+ assert released, 'Failed to release lock using gem signature'
45
+
46
+ # Verify lock is released by trying to acquire it again
47
+ result = model_class.connection.try_advisory_lock(
48
+ lock_keys,
49
+ lock_name: lock_name,
50
+ shared: false,
51
+ transaction: false
52
+ )
53
+ assert result, 'Lock was not properly released'
54
+
55
+ # Clean up
56
+ model_class.connection.release_advisory_lock(
57
+ lock_keys,
58
+ lock_name: lock_name,
59
+ shared: false,
60
+ transaction: false
61
+ )
62
+ end
63
+
64
+ test 'release_advisory_lock handles ActiveRecord signature' do
65
+ # Rails calls release_advisory_lock with a positional argument (lock_id)
66
+ # This test ensures our override doesn't break Rails' migration locking
67
+
68
+ lock_name = 'test_rails_signature'
69
+
70
+ # Acquire lock using SQL (ActiveRecord doesn't provide get_advisory_lock method)
71
+ lock_keys = model_class.connection.lock_keys_for(lock_name)
72
+ result = model_class.connection.query_value("SELECT GET_LOCK(#{model_class.connection.quote(lock_keys.first)}, 0)")
73
+ assert_equal 1, result, 'Failed to acquire lock using SQL'
74
+
75
+ # Release using ActiveRecord signature (positional argument, as Rails does)
76
+ released = model_class.connection.release_advisory_lock(lock_keys.first)
77
+ assert released, 'Failed to release lock using ActiveRecord signature'
78
+
79
+ # Verify lock is released
80
+ lock_keys = model_class.connection.lock_keys_for(lock_name)
81
+ result = model_class.connection.query_value("SELECT GET_LOCK(#{model_class.connection.quote(lock_keys.first)}, 0)")
82
+ assert_equal 1, result, 'Lock was not properly released'
83
+
84
+ # Clean up
85
+ model_class.connection.query_value("SELECT RELEASE_LOCK(#{model_class.connection.quote(lock_keys.first)})")
86
+ end
87
+
88
+ test 'release_advisory_lock handles connection errors gracefully' do
89
+ lock_name = 'test_connection_error'
90
+ lock_keys = model_class.connection.lock_keys_for(lock_name)
91
+
92
+ # Acquire the lock
93
+ result = model_class.connection.try_advisory_lock(
94
+ lock_keys,
95
+ lock_name: lock_name,
96
+ shared: false,
97
+ transaction: false
98
+ )
99
+ assert result, 'Failed to acquire lock'
100
+
101
+ # Simulate connection error handling
102
+ # The method should handle various connection error types without raising
103
+ begin
104
+ # Try to release - even if we can't simulate a real connection error,
105
+ # the code path exists and should work
106
+ model_class.connection.release_advisory_lock(
107
+ lock_keys,
108
+ lock_name: lock_name,
109
+ shared: false,
110
+ transaction: false
111
+ )
112
+ rescue StandardError => e
113
+ # Should not raise connection-related errors
114
+ refute_match(/Lost connection|MySQL server has gone away|Connection refused/i, e.message)
115
+ raise
116
+ end
117
+ end
118
+ end
@@ -7,8 +7,8 @@ class PostgreSQLTransactionScopingTest < GemTestCase
7
7
 
8
8
  setup do
9
9
  @pg_lock_count = lambda do
10
- backend_pid = Tag.connection.select_value('SELECT pg_backend_pid()')
11
- Tag.connection.select_value("SELECT COUNT(*) FROM pg_locks WHERE locktype = 'advisory' AND pid = #{backend_pid};").to_i
10
+ backend_pid = Tag.connection.query_value('SELECT pg_backend_pid()')
11
+ Tag.connection.query_value("SELECT COUNT(*) FROM pg_locks WHERE locktype = 'advisory' AND pid = #{backend_pid};").to_i
12
12
  end
13
13
  end
14
14
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: with_advisory_lock
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
@@ -157,6 +157,7 @@ files:
157
157
  - test/with_advisory_lock/concern_test.rb
158
158
  - test/with_advisory_lock/lock_test.rb
159
159
  - test/with_advisory_lock/multi_adapter_test.rb
160
+ - test/with_advisory_lock/mysql_release_lock_test.rb
160
161
  - test/with_advisory_lock/parallelism_test.rb
161
162
  - test/with_advisory_lock/postgresql_race_condition_test.rb
162
163
  - test/with_advisory_lock/shared_test.rb