with_advisory_lock 7.5.0 → 7.6.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
  SHA256:
3
- metadata.gz: b1b30155d5acce8e77bf5fa733d0b4f257d34d3ccf4a40a3f2112314a34b83d9
4
- data.tar.gz: 6c844c642c330044dd1e05355678fb862a5549ce9173fb794ee1d269cbe0cde9
3
+ metadata.gz: b73961f538d06e085eda202c9acac95b86e248dfc368cefa5267947ed8f988a2
4
+ data.tar.gz: c6b025af1bc1f16ea12b104da20fcfbd6586347eb23b72de47eafd09b8c4e9f0
5
5
  SHA512:
6
- metadata.gz: bb0d2c8836c60963bc5c6dab3aaf3d7831ee1a2c8e86346970b77bf0de64779e0a5cef6a8965d89c0674a5daf0e892584d64116d9825e0e1bb9983d3e5698910
7
- data.tar.gz: cc763d1ff02720ece882a9c9d0973818ac72f8774538c8d91848b4f0f9eac943b125fbecc01e9ca9d24a033977740ac189988e8225b878ad2010ac70a02826d7
6
+ metadata.gz: b11f600e41ed75fe00f7314d2cc090547e941ef4a9562c98e4573e892a15d3429b6609a1475f170a20f2d5197aba6dac153596e58181d6092db0d05c0adcc4fc
7
+ data.tar.gz: 24e45f293e0d2d653cc58e772b11f70d8d9691aac66a4d4be64cc81d67f322e6e00520e83d23613c0a176b74ca7137d60c5de8036b837a1b67b39eab3cad221e
@@ -1 +1 @@
1
- {".":"7.5.0"}
1
+ {".":"7.6.0"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## Changelog
2
2
 
3
+ ## [7.6.0](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v7.5.0...with_advisory_lock/v7.6.0) (2026-08-05)
4
+
5
+
6
+ ### Features
7
+
8
+ * honor timeout_seconds with blocking: true on PostgreSQL ([88abf06](https://github.com/ClosureTree/with_advisory_lock/commit/88abf0689b2ac7045c1617fe5fcac0bedff61adf)), closes [#147](https://github.com/ClosureTree/with_advisory_lock/issues/147)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Remove ruby-version files ([#145](https://github.com/ClosureTree/with_advisory_lock/issues/145)) ([681fd66](https://github.com/ClosureTree/with_advisory_lock/commit/681fd66f1d45800897f2d77ac8905474910a90ff))
14
+ * scope advisory lock stack per connection ([8c39439](https://github.com/ClosureTree/with_advisory_lock/commit/8c394392872f0d9d6ab6b83d1185eeb291de2409))
15
+
3
16
  ## [7.5.0](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v7.0.2...with_advisory_lock/v7.5.0) (2026-01-21)
4
17
 
5
18
 
@@ -72,7 +85,7 @@
72
85
 
73
86
  ### Features
74
87
 
75
- * use current connnection instead of the one in ActiveRecord::Base ([#90](https://github.com/ClosureTree/with_advisory_lock/issues/90)) ([c28a172](https://github.com/ClosureTree/with_advisory_lock/commit/c28a172a5a64594448b6090501fc0b8cbace06f6))
88
+ * use current connection instead of the one in ActiveRecord::Base ([#90](https://github.com/ClosureTree/with_advisory_lock/issues/90)) ([c28a172](https://github.com/ClosureTree/with_advisory_lock/commit/c28a172a5a64594448b6090501fc0b8cbace06f6))
76
89
 
77
90
 
78
91
  ### Bug Fixes
data/README.md CHANGED
@@ -52,7 +52,7 @@ called.
52
52
  > If a non-nil value is provided for `timeout_seconds`, the block will
53
53
  *not* be invoked if the lock cannot be acquired within that time-frame. In this case, `with_advisory_lock` will return `false`, while `with_advisory_lock!` will raise a `WithAdvisoryLock::FailedToAcquireLock` error.
54
54
 
55
- For backwards compatability, the timeout value can be specified directly as the
55
+ For backwards compatibility, the timeout value can be specified directly as the
56
56
  second parameter.
57
57
 
58
58
  ### Shared locks
@@ -95,9 +95,31 @@ end
95
95
  - When you need PostgreSQL to detect and break circular lock dependencies
96
96
  - When you want to avoid Ruby-level polling overhead
97
97
 
98
+ **Combining with a timeout:** `blocking: true` together with `timeout_seconds`
99
+ bounds the database-level wait using a transaction-scoped
100
+ `SET LOCAL lock_timeout`, returning `false` if the lock cannot be acquired in
101
+ time. The setting never leaks to the session, and a timeout inside an enclosing
102
+ transaction rolls back to a savepoint instead of aborting your transaction.
103
+ A `timeout_seconds` of `0` degrades to the non-blocking try path.
104
+
105
+ ```ruby
106
+ User.with_advisory_lock("lock_name", blocking: true, timeout_seconds: 5) do
107
+ # waits up to 5 seconds at the database level, no Ruby polling
108
+ end
109
+ ```
110
+
98
111
  **Note:** MySQL ignores this option since `GET_LOCK` already provides native
99
112
  timeout and deadlock detection via the MDL subsystem.
100
113
 
114
+ ### Connection poolers (PgBouncer)
115
+
116
+ Session-level advisory locks bind to the PostgreSQL backend connection, so they
117
+ are incompatible with PgBouncer in *transaction pooling* mode: statements
118
+ outside a transaction may run on different backends, stranding or leaking
119
+ locks. Under transaction pooling, use `transaction: true` locks, which stay
120
+ pinned to one backend for the duration of the transaction. Session pooling and
121
+ direct connections are unaffected.
122
+
101
123
  ### Return values
102
124
 
103
125
  The return value of `with_advisory_lock_result` is a `WithAdvisoryLock::Result`
@@ -194,4 +216,3 @@ You will want to wrap your block within a transaction to ensure consistency.
194
216
  ### Is clustered MySQL supported?
195
217
 
196
218
  [No.](https://github.com/ClosureTree/with_advisory_lock/issues/16)
197
-
data/docker-compose.yml CHANGED
@@ -2,16 +2,16 @@ services:
2
2
  pg:
3
3
  image: postgres:17-alpine
4
4
  environment:
5
- POSTGRES_USER: test
6
- POSTGRES_PASSWORD: test
5
+ POSTGRES_USER: with_advisory
6
+ POSTGRES_PASSWORD: with_advisory_pass
7
7
  POSTGRES_DB: with_advisory_lock_test
8
8
  ports:
9
9
  - "5433:5432"
10
10
  mysql:
11
11
  image: mysql:8
12
12
  environment:
13
- MYSQL_USER: test
14
- MYSQL_PASSWORD: test
13
+ MYSQL_USER: with_advisory
14
+ MYSQL_PASSWORD: with_advisory_pass
15
15
  MYSQL_DATABASE: with_advisory_lock_test
16
16
  MYSQL_RANDOM_ROOT_PASSWORD: "yes"
17
17
  MYSQL_ROOT_HOST: '%'
@@ -20,9 +20,9 @@ services:
20
20
  mariadb:
21
21
  image: mariadb:12
22
22
  environment:
23
- MARIADB_USER: test
24
- MARIADB_PASSWORD: test
25
- MARIADB_DATABASE: with_advisory_lock_test_trilogy
23
+ MARIADB_USER: with_advisory
24
+ MARIADB_PASSWORD: with_advisory_pass
25
+ MARIADB_DATABASE: with_advisory_lock_trilogy_test
26
26
  MARIADB_RANDOM_ROOT_PASSWORD: "yes"
27
27
  MARIADB_ROOT_HOST: '%'
28
28
  ports:
@@ -24,22 +24,22 @@ module WithAdvisoryLock
24
24
  end
25
25
  end
26
26
 
27
- def advisory_lock_exists?(lock_name)
27
+ def advisory_lock_exists?(lock_name, shared: false)
28
28
  with_connection do |conn|
29
29
  lock_str = "#{ENV.fetch(CoreAdvisory::LOCK_PREFIX_ENV, nil)}#{lock_name}"
30
- lock_stack_item = LockStackItem.new(lock_str, false)
30
+ lock_stack_item = LockStackItem.new(lock_str, shared)
31
31
 
32
32
  if conn.advisory_lock_stack.include?(lock_stack_item)
33
33
  true
34
34
  else
35
- # For PostgreSQL, try non-blocking query first to avoid race conditions
35
+ # Prefer the adapter's non-blocking existence check
36
36
  if conn.respond_to?(:advisory_lock_exists_for?)
37
- query_result = conn.advisory_lock_exists_for?(lock_name)
37
+ query_result = conn.advisory_lock_exists_for?(lock_name, shared: shared)
38
38
  return query_result unless query_result.nil?
39
39
  end
40
40
 
41
41
  # Fall back to the original implementation
42
- result = conn.with_advisory_lock_if_needed(lock_name, { timeout_seconds: 0 })
42
+ result = conn.with_advisory_lock_if_needed(lock_name, { timeout_seconds: 0, shared: shared })
43
43
  !result.lock_was_acquired?
44
44
  end
45
45
  end
@@ -8,9 +8,9 @@ module WithAdvisoryLock
8
8
 
9
9
  LOCK_PREFIX_ENV = 'WITH_ADVISORY_LOCK_PREFIX'
10
10
 
11
- # Thread-local lock stack management
11
+ # Per-connection lock stack management
12
12
  def advisory_lock_stack
13
- Thread.current[:with_advisory_lock_stack] ||= []
13
+ @advisory_lock_stack ||= []
14
14
  end
15
15
 
16
16
  def with_advisory_lock_if_needed(lock_name, options = {}, &block)
@@ -57,6 +57,8 @@ module WithAdvisoryLock
57
57
  shared = options.fetch(:shared, false)
58
58
  transaction = options.fetch(:transaction, false)
59
59
  blocking = options.fetch(:blocking, false)
60
+ # An explicit zero timeout means try-once, so blocking degrades to the try path
61
+ blocking = false if timeout_seconds&.zero?
60
62
 
61
63
  lock_keys = lock_keys_for(lock_name)
62
64
 
@@ -6,8 +6,6 @@ module WithAdvisoryLock
6
6
  module MySQLAdvisory
7
7
  extend ActiveSupport::Concern
8
8
 
9
- LOCK_PREFIX_ENV = 'WITH_ADVISORY_LOCK_PREFIX'
10
-
11
9
  def try_advisory_lock(lock_keys, lock_name:, shared:, transaction:, timeout_seconds: nil, blocking: false)
12
10
  raise ArgumentError, 'shared locks are not supported on MySQL' if shared
13
11
  raise ArgumentError, 'transaction level locks are not supported on MySQL' if transaction
@@ -46,22 +44,11 @@ module WithAdvisoryLock
46
44
  rescue ActiveRecord::StatementInvalid => e
47
45
  # If the connection is broken, the lock is automatically released by MySQL
48
46
  # No need to fail the release operation
49
- connection_lost = case e.cause
50
- when defined?(Mysql2::Error::ConnectionError) && Mysql2::Error::ConnectionError
51
- true
52
- when defined?(Trilogy::ConnectionError) && Trilogy::ConnectionError
53
- true
54
- else
55
- e.message =~ /Lost connection|MySQL server has gone away|Connection refused/i
56
- end
57
-
58
- return if connection_lost
59
-
60
- raise
47
+ raise unless connection_lost_error?(e)
61
48
  end
62
49
 
63
50
  def lock_keys_for(lock_name)
64
- lock_str = "#{ENV.fetch(LOCK_PREFIX_ENV, nil)}#{lock_name}"
51
+ lock_str = "#{ENV.fetch(CoreAdvisory::LOCK_PREFIX_ENV, nil)}#{lock_name}"
65
52
  [lock_str]
66
53
  end
67
54
 
@@ -69,8 +56,26 @@ module WithAdvisoryLock
69
56
  true
70
57
  end
71
58
 
59
+ # Non-blocking check via IS_USED_LOCK, no lock is acquired as a side effect
60
+ def advisory_lock_exists_for?(lock_name, shared: false)
61
+ raise ArgumentError, 'shared locks are not supported on MySQL' if shared
62
+
63
+ lock_keys = lock_keys_for(lock_name)
64
+ !query_value("SELECT IS_USED_LOCK(#{quote(lock_keys.first)})").nil?
65
+ rescue ActiveRecord::StatementInvalid
66
+ nil
67
+ end
68
+
72
69
  private
73
70
 
71
+ def connection_lost_error?(error)
72
+ cause = error.cause
73
+ return true if defined?(Mysql2::Error::ConnectionError) && cause.is_a?(Mysql2::Error::ConnectionError)
74
+ return true if defined?(Trilogy::ConnectionError) && cause.is_a?(Trilogy::ConnectionError)
75
+
76
+ error.message.match?(/Lost connection|MySQL server has gone away|Connection refused/i)
77
+ end
78
+
74
79
  def execute_successful?(mysql_function)
75
80
  query_value("SELECT #{mysql_function}") == 1
76
81
  end
@@ -6,19 +6,21 @@ module WithAdvisoryLock
6
6
  module PostgreSQLAdvisory
7
7
  extend ActiveSupport::Concern
8
8
 
9
- LOCK_PREFIX_ENV = 'WITH_ADVISORY_LOCK_PREFIX'
10
9
  LOCK_RESULT_VALUES = ['t', true].freeze
11
10
  ERROR_MESSAGE_REGEX = / ERROR: +current transaction is aborted,/
12
11
 
13
12
  def try_advisory_lock(lock_keys, lock_name:, shared:, transaction:, timeout_seconds: nil, blocking: false)
14
- # timeout_seconds is accepted for compatibility but ignored - PostgreSQL doesn't support
15
- # native timeouts with pg_try_advisory_lock, requiring Ruby-level polling instead
16
13
  function = if blocking
17
14
  advisory_lock_function(transaction, shared)
18
15
  else
19
16
  advisory_try_lock_function(transaction, shared)
20
17
  end
21
- execute_advisory(function, lock_keys, lock_name, blocking: blocking)
18
+
19
+ if blocking && timeout_seconds&.positive?
20
+ blocking_lock_with_timeout(function, lock_keys, lock_name, timeout_seconds)
21
+ else
22
+ execute_advisory(function, lock_keys, lock_name, blocking: blocking)
23
+ end
22
24
  rescue ActiveRecord::Deadlocked
23
25
  # Rails 8.2+ raises ActiveRecord::Deadlocked directly for PostgreSQL deadlocks
24
26
  # When using blocking locks, treat deadlocks as lock acquisition failure
@@ -40,37 +42,36 @@ module WithAdvisoryLock
40
42
  end
41
43
 
42
44
  def release_advisory_lock(*args)
43
- # Handle both signatures - ActiveRecord's built-in and ours
44
- if args.length == 1 && args[0].is_a?(Integer)
45
- # ActiveRecord's built-in signature: release_advisory_lock(lock_id)
46
- super
47
- else
48
- # Our signature: release_advisory_lock(lock_keys, lock_name:, shared:, transaction:)
49
- lock_keys, options = args
50
- return if options[:transaction]
51
-
52
- function = advisory_unlock_function(options[:shared])
53
- execute_advisory(function, lock_keys, options[:lock_name])
54
- end
55
- rescue ActiveRecord::StatementInvalid => e
56
- # If the connection is broken, the lock is automatically released by PostgreSQL
57
- # No need to fail the release operation
58
- return if e.cause.is_a?(PG::ConnectionBad) || e.message =~ /PG::ConnectionBad/
45
+ # ActiveRecord's built-in signature: release_advisory_lock(lock_id)
46
+ return super if args.length == 1 && args[0].is_a?(Integer)
59
47
 
60
- raise unless e.message =~ ERROR_MESSAGE_REGEX
48
+ # Our signature: release_advisory_lock(lock_keys, lock_name:, shared:, transaction:)
49
+ lock_keys, options = args
50
+ return if options[:transaction]
61
51
 
52
+ function = advisory_unlock_function(options[:shared])
62
53
  begin
63
- rollback_db_transaction
64
54
  execute_advisory(function, lock_keys, options[:lock_name])
65
- ensure
66
- begin_db_transaction
55
+ rescue ActiveRecord::StatementInvalid => e
56
+ # If the connection is broken, the lock is automatically released by PostgreSQL
57
+ # No need to fail the release operation
58
+ return if e.cause.is_a?(PG::ConnectionBad) || e.message =~ /PG::ConnectionBad/
59
+
60
+ raise unless e.message =~ ERROR_MESSAGE_REGEX
61
+
62
+ begin
63
+ rollback_db_transaction
64
+ execute_advisory(function, lock_keys, options[:lock_name])
65
+ ensure
66
+ begin_db_transaction
67
+ end
67
68
  end
68
69
  end
69
70
 
70
71
  def lock_keys_for(lock_name)
71
72
  [
72
73
  stable_hashcode(lock_name),
73
- ENV.fetch(LOCK_PREFIX_ENV, nil)
74
+ ENV.fetch(CoreAdvisory::LOCK_PREFIX_ENV, nil)
74
75
  ].map { |ea| ea.to_i & 0x7fffffff }
75
76
  end
76
77
 
@@ -90,6 +91,7 @@ module WithAdvisoryLock
90
91
  AND classid = #{lock_keys.first}
91
92
  AND objid = #{lock_keys.last}
92
93
  AND mode = '#{shared ? 'ShareLock' : 'ExclusiveLock'}'
94
+ AND granted
93
95
  LIMIT 1
94
96
  SQL
95
97
 
@@ -101,6 +103,23 @@ module WithAdvisoryLock
101
103
 
102
104
  private
103
105
 
106
+ # Bounded blocking acquisition via SET LOCAL lock_timeout. The setting is
107
+ # transaction-scoped so it never leaks to the session (pooler-safe), and
108
+ # the savepoint keeps a timeout from aborting an enclosing transaction.
109
+ # Session-level locks acquired here survive the wrapping COMMIT.
110
+ def blocking_lock_with_timeout(function, lock_keys, lock_name, timeout_seconds)
111
+ transaction(requires_new: true) do
112
+ previous_timeout = query_value('SHOW lock_timeout')
113
+ execute("SET LOCAL lock_timeout = '#{(timeout_seconds * 1000).to_i}ms'")
114
+ execute_advisory(function, lock_keys, lock_name, blocking: true)
115
+ # SET LOCAL survives a released savepoint, so restore the outer value
116
+ execute("SET LOCAL lock_timeout = #{quote(previous_timeout)}")
117
+ true
118
+ end
119
+ rescue ActiveRecord::LockWaitTimeout
120
+ false
121
+ end
122
+
104
123
  def advisory_try_lock_function(transaction_scope, shared)
105
124
  [
106
125
  'pg_try_advisory',
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WithAdvisoryLock
4
- VERSION = Gem::Version.new('7.5.0')
4
+ VERSION = Gem::Version.new('7.6.0')
5
5
  end
@@ -128,6 +128,80 @@ if GemTestCase.trilogy_available?
128
128
  end
129
129
  end
130
130
 
131
+ # Requires non-transactional mode: transactional tests pin all threads to a
132
+ # single shared connection, so a "holder" thread would share the session
133
+ class PostgreSQLBlockingTimeoutTest < GemTestCase
134
+ self.use_transactional_tests = false
135
+
136
+ setup do
137
+ @lock_name = 'blocking_timeout_lock'
138
+ @release = false
139
+ end
140
+
141
+ teardown do
142
+ @release = true
143
+ @holder&.join
144
+ end
145
+
146
+ def hold_lock_elsewhere
147
+ locked = false
148
+ @holder = Thread.new do
149
+ Tag.connection_pool.with_connection do
150
+ Tag.with_advisory_lock(@lock_name, timeout_seconds: 0) do
151
+ locked = true
152
+ sleep 0.05 until @release
153
+ end
154
+ end
155
+ end
156
+ sleep 0.01 until locked
157
+ end
158
+
159
+ test 'blocking lock with timeout returns false when lock is held elsewhere' do
160
+ hold_lock_elsewhere
161
+
162
+ elapsed = Benchmark.realtime do
163
+ result = Tag.with_advisory_lock(@lock_name, blocking: true, timeout_seconds: 1) { 'unreachable' }
164
+ assert_equal false, result
165
+ end
166
+ assert elapsed >= 0.9, "expected to wait for the timeout, waited #{elapsed}s"
167
+ assert elapsed < 5.0, "expected timeout after ~1s, waited #{elapsed}s"
168
+ end
169
+
170
+ test 'blocking lock with timeout acquires when lock is free' do
171
+ result = Tag.with_advisory_lock(@lock_name, blocking: true, timeout_seconds: 1) { 'acquired' }
172
+ assert_equal 'acquired', result
173
+ end
174
+
175
+ test 'blocking lock with timeout does not leak lock_timeout' do
176
+ Tag.with_connection do |conn|
177
+ previous = conn.query_value('SHOW lock_timeout')
178
+ conn.with_advisory_lock_if_needed(@lock_name, { blocking: true, timeout_seconds: 1 }) { 'x' }
179
+ assert_equal previous, conn.query_value('SHOW lock_timeout')
180
+ end
181
+ end
182
+
183
+ test 'blocking lock with timeout failure does not abort enclosing transaction' do
184
+ hold_lock_elsewhere
185
+
186
+ Tag.transaction do
187
+ result = Tag.with_advisory_lock(@lock_name, blocking: true, timeout_seconds: 1) { 'unreachable' }
188
+ assert_equal false, result
189
+ # The savepoint rollback must leave the transaction usable
190
+ assert Tag.count >= 0
191
+ end
192
+ end
193
+
194
+ test 'blocking lock with zero timeout uses the try path' do
195
+ hold_lock_elsewhere
196
+
197
+ elapsed = Benchmark.realtime do
198
+ result = Tag.with_advisory_lock(@lock_name, blocking: true, timeout_seconds: 0) { 'unreachable' }
199
+ assert_equal false, result
200
+ end
201
+ assert elapsed < 1.0, "expected immediate return, waited #{elapsed}s"
202
+ end
203
+ end
204
+
131
205
  # Deadlock test requires non-transactional mode to work properly
132
206
  class PostgreSQLDeadlockTest < GemTestCase
133
207
  self.use_transactional_tests = false
@@ -2,45 +2,89 @@
2
2
 
3
3
  require 'test_helper'
4
4
 
5
+ # Database-level assertions so these tests cannot pass without real lock acquisition
6
+ module AdapterLockAssertions
7
+ def assert_mysql_lock_held(model, lock_name)
8
+ model.with_connection do |conn|
9
+ key = conn.lock_keys_for(lock_name).first
10
+ assert_not_nil conn.query_value("SELECT IS_USED_LOCK(#{conn.quote(key)})"),
11
+ "#{model.name} should hold GET_LOCK(#{key.inspect}) at the database level"
12
+ end
13
+ end
14
+
15
+ def assert_pg_lock_held(model, lock_name)
16
+ model.with_connection do |conn|
17
+ assert conn.advisory_lock_exists_for?(lock_name),
18
+ "#{model.name} should hold the advisory lock in pg_locks"
19
+ end
20
+ end
21
+ end
22
+
5
23
  class MultiAdapterIsolationTest < GemTestCase
24
+ include AdapterLockAssertions
25
+
6
26
  test 'postgresql and mysql adapters do not overlap' do
7
27
  lock_name = 'multi-adapter-lock'
8
28
 
9
29
  # PostgreSQL lock doesn't block MySQL
10
30
  Tag.with_advisory_lock(lock_name) do
11
- assert MysqlTag.with_advisory_lock(lock_name, timeout_seconds: 0) { true }
31
+ acquired = MysqlTag.with_advisory_lock(lock_name, timeout_seconds: 0) do
32
+ assert_mysql_lock_held(MysqlTag, lock_name)
33
+ true
34
+ end
35
+ assert acquired
12
36
  end
13
37
 
14
38
  # MySQL lock doesn't block PostgreSQL
15
39
  MysqlTag.with_advisory_lock(lock_name) do
16
- assert Tag.with_advisory_lock(lock_name, timeout_seconds: 0) { true }
40
+ acquired = Tag.with_advisory_lock(lock_name, timeout_seconds: 0) do
41
+ assert_pg_lock_held(Tag, lock_name)
42
+ true
43
+ end
44
+ assert acquired
17
45
  end
18
46
  end
19
47
  end
20
48
 
21
49
  if GemTestCase.trilogy_available?
22
50
  class TrilogyMultiAdapterIsolationTest < GemTestCase
51
+ include AdapterLockAssertions
23
52
  test 'trilogy adapter does not overlap with postgresql or mysql' do
24
53
  lock_name = 'multi-adapter-lock'
25
54
 
26
55
  # PostgreSQL lock doesn't block Trilogy
27
56
  Tag.with_advisory_lock(lock_name) do
28
- assert TrilogyTag.with_advisory_lock(lock_name, timeout_seconds: 0) { true }
57
+ acquired = TrilogyTag.with_advisory_lock(lock_name, timeout_seconds: 0) do
58
+ assert_mysql_lock_held(TrilogyTag, lock_name)
59
+ true
60
+ end
61
+ assert acquired
29
62
  end
30
63
 
31
64
  # Trilogy lock doesn't block PostgreSQL
32
65
  TrilogyTag.with_advisory_lock(lock_name) do
33
- assert Tag.with_advisory_lock(lock_name, timeout_seconds: 0) { true }
66
+ acquired = Tag.with_advisory_lock(lock_name, timeout_seconds: 0) do
67
+ assert_pg_lock_held(Tag, lock_name)
68
+ true
69
+ end
70
+ assert acquired
34
71
  end
35
72
 
36
73
  # MySQL lock doesn't block Trilogy
37
74
  MysqlTag.with_advisory_lock(lock_name) do
38
- assert TrilogyTag.with_advisory_lock(lock_name, timeout_seconds: 0) { true }
75
+ acquired = TrilogyTag.with_advisory_lock(lock_name, timeout_seconds: 0) do
76
+ assert_mysql_lock_held(TrilogyTag, lock_name)
77
+ true
78
+ end
79
+ assert acquired
39
80
  end
40
81
 
41
- # Trilogy lock doesn't block MySQL
42
82
  TrilogyTag.with_advisory_lock(lock_name) do
43
- assert MysqlTag.with_advisory_lock(lock_name, timeout_seconds: 0) { true }
83
+ acquired = MysqlTag.with_advisory_lock(lock_name, timeout_seconds: 0) do
84
+ assert_mysql_lock_held(MysqlTag, lock_name)
85
+ true
86
+ end
87
+ assert acquired
44
88
  end
45
89
  end
46
90
  end
@@ -24,7 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.metadata['changelog_uri'] = 'https://github.com/ClosureTree/with_advisory_lock/blob/master/CHANGELOG.md'
25
25
 
26
26
  spec.add_dependency 'activerecord', '>= 7.2'
27
- spec.add_dependency 'zeitwerk', '>= 2.7'
28
27
 
29
28
  spec.add_development_dependency 'maxitest', '6.2.0'
30
29
  spec.add_development_dependency 'minitest-reporters'
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.5.0
4
+ version: 7.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7.2'
27
- - !ruby/object:Gem::Dependency
28
- name: zeitwerk
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '2.7'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '2.7'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: maxitest
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -106,8 +92,6 @@ files:
106
92
  - ".github/workflows/release.yml"
107
93
  - ".gitignore"
108
94
  - ".release-please-manifest.json"
109
- - ".ruby-version"
110
- - ".tool-versions"
111
95
  - CHANGELOG.md
112
96
  - Gemfile
113
97
  - LICENSE.txt
@@ -193,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
177
  - !ruby/object:Gem::Version
194
178
  version: '0'
195
179
  requirements: []
196
- rubygems_version: 4.0.3
180
+ rubygems_version: 4.0.10
197
181
  specification_version: 4
198
182
  summary: Advisory locking for ActiveRecord
199
183
  test_files: []
data/.ruby-version DELETED
@@ -1,2 +0,0 @@
1
- 3.4.4
2
-
data/.tool-versions DELETED
@@ -1 +0,0 @@
1
- ruby 4.0.1