with_advisory_lock 7.0.2 → 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.
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TrilogyTag < TrilogyRecord
4
+ self.table_name = 'trilogy_tags'
5
+
6
+ after_save do
7
+ TrilogyTagAudit.create(tag_name: name)
8
+ TrilogyLabel.create(name: name)
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TrilogyTagAudit < TrilogyRecord
4
+ self.table_name = 'trilogy_tag_audits'
5
+ end
@@ -14,6 +14,17 @@ module TestSystemApp
14
14
  class Application < Rails::Application
15
15
  config.load_defaults [Rails::VERSION::MAJOR, Rails::VERSION::MINOR].join('.')
16
16
  config.eager_load = true
17
+
18
+ # Ignore trilogy models when DATABASE_URL_TRILOGY is not set (e.g., TruffleRuby)
19
+ unless ENV['DATABASE_URL_TRILOGY'] && !ENV['DATABASE_URL_TRILOGY'].empty?
20
+ config.autoload_lib(ignore: %w[])
21
+ initializer 'ignore_trilogy_models', before: :set_autoload_paths do |app|
22
+ trilogy_models = %w[trilogy_record trilogy_tag trilogy_tag_audit trilogy_label]
23
+ trilogy_models.each do |model|
24
+ Rails.autoloaders.main.ignore(Rails.root.join('app', 'models', "#{model}.rb"))
25
+ end
26
+ end
27
+ end
17
28
  config.serve_static_files = false
18
29
  config.public_file_server.enabled = false
19
30
  config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
@@ -11,3 +11,11 @@ test:
11
11
  url: "<%= ENV['DATABASE_URL_MYSQL'] %>"
12
12
  properties:
13
13
  allowPublicKeyRetrieval: true
14
+ <% if ENV['DATABASE_URL_TRILOGY'] && !ENV['DATABASE_URL_TRILOGY'].empty? %>
15
+ trilogy:
16
+ <<: *default
17
+ url: "<%= ENV['DATABASE_URL_TRILOGY'] %>"
18
+ adapter: trilogy
19
+ properties:
20
+ allowPublicKeyRetrieval: true
21
+ <% end %>
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define(version: 1) do
4
+ create_table 'trilogy_tags', force: true do |t|
5
+ t.string 'name'
6
+ end
7
+
8
+ create_table 'trilogy_tag_audits', id: false, force: true do |t|
9
+ t.string 'tag_name'
10
+ end
11
+
12
+ create_table 'trilogy_labels', id: false, force: true do |t|
13
+ t.string 'name'
14
+ end
15
+ end
@@ -4,37 +4,29 @@ namespace :db do
4
4
  namespace :test do
5
5
  desc 'Load schema for all databases'
6
6
  task prepare: :environment do
7
- # Load schema for primary database
7
+ # Setup PostgreSQL database
8
8
  ActiveRecord::Base.establish_connection(:primary)
9
- ActiveRecord::Schema.define(version: 1) do
10
- create_table 'tags', force: true do |t|
11
- t.string 'name'
12
- end
9
+ load Rails.root.join('db', 'schema.rb')
10
+ puts 'PostgreSQL database schema loaded'
13
11
 
14
- create_table 'tag_audits', id: false, force: true do |t|
15
- t.string 'tag_name'
16
- end
17
-
18
- create_table 'labels', id: false, force: true do |t|
19
- t.string 'name'
20
- end
21
- end
22
-
23
- # Load schema for secondary database
12
+ # Setup MySQL database
24
13
  ActiveRecord::Base.establish_connection(:secondary)
25
- ActiveRecord::Schema.define(version: 1) do
26
- create_table 'mysql_tags', force: true do |t|
27
- t.string 'name'
28
- end
14
+ load Rails.root.join('db', 'secondary_schema.rb')
15
+ puts 'MySQL database schema loaded'
29
16
 
30
- create_table 'mysql_tag_audits', id: false, force: true do |t|
31
- t.string 'tag_name'
32
- end
33
-
34
- create_table 'mysql_labels', id: false, force: true do |t|
35
- t.string 'name'
36
- end
17
+ # Setup Trilogy database (MariaDB) - optional, not supported on TruffleRuby
18
+ if ENV['DATABASE_URL_TRILOGY'] && !ENV['DATABASE_URL_TRILOGY'].empty?
19
+ ActiveRecord::Base.establish_connection(:trilogy)
20
+ load Rails.root.join('db', 'trilogy_schema.rb')
21
+ puts 'Trilogy database schema loaded'
22
+ else
23
+ puts 'Skipping Trilogy database (DATABASE_URL_TRILOGY not set)'
37
24
  end
25
+
26
+ puts 'All test databases prepared successfully'
27
+ rescue StandardError => e
28
+ puts "Error preparing test databases: #{e.message}"
29
+ raise e
38
30
  end
39
31
  end
40
32
  end
@@ -43,7 +43,7 @@ class SanityCheckTest < GemTestCase
43
43
  assert_equal 'Mysql2', MysqlLabel.connection.adapter_name
44
44
  end
45
45
 
46
- test 'can write to both databases in same test' do
46
+ test 'can write to PostgreSQL and MySQL databases in same test' do
47
47
  # Create records in both databases
48
48
  pg_tag = Tag.create!(name: 'test-pg')
49
49
  mysql_tag = MysqlTag.create!(name: 'test-mysql')
@@ -61,3 +61,50 @@ class SanityCheckTest < GemTestCase
61
61
  mysql_tag.destroy
62
62
  end
63
63
  end
64
+
65
+ if GemTestCase.trilogy_available?
66
+ class TrilogySanityCheckTest < GemTestCase
67
+ test 'Trilogy database is isolated from PostgreSQL and MySQL' do
68
+ # Create tags in all databases
69
+ pg_tag = Tag.create!(name: 'pg-isolation-test')
70
+ mysql_tag = MysqlTag.create!(name: 'mysql-isolation-test')
71
+ trilogy_tag = TrilogyTag.create!(name: 'trilogy-isolation-test')
72
+
73
+ # Verify Trilogy tag exists only in Trilogy
74
+ assert TrilogyTag.exists?(name: 'trilogy-isolation-test')
75
+ assert_not Tag.exists?(name: 'trilogy-isolation-test')
76
+ assert_not MysqlTag.exists?(name: 'trilogy-isolation-test')
77
+
78
+ # Verify PostgreSQL tag doesn't exist in Trilogy
79
+ assert_not TrilogyTag.exists?(name: 'pg-isolation-test')
80
+
81
+ # Verify MySQL tag doesn't exist in Trilogy
82
+ assert_not TrilogyTag.exists?(name: 'mysql-isolation-test')
83
+
84
+ # Clean up
85
+ pg_tag.destroy
86
+ mysql_tag.destroy
87
+ trilogy_tag.destroy
88
+ end
89
+
90
+ test 'Trilogy models use Trilogy adapter' do
91
+ assert_equal 'Trilogy', TrilogyTag.connection.adapter_name
92
+ assert_equal 'Trilogy', TrilogyTagAudit.connection.adapter_name
93
+ assert_equal 'Trilogy', TrilogyLabel.connection.adapter_name
94
+ end
95
+
96
+ test 'can write to all three databases in same test' do
97
+ pg_tag = Tag.create!(name: 'test-pg')
98
+ mysql_tag = MysqlTag.create!(name: 'test-mysql')
99
+ trilogy_tag = TrilogyTag.create!(name: 'test-trilogy')
100
+
101
+ assert pg_tag.persisted?
102
+ assert mysql_tag.persisted?
103
+ assert trilogy_tag.persisted?
104
+
105
+ pg_tag.destroy
106
+ mysql_tag.destroy
107
+ trilogy_tag.destroy
108
+ end
109
+ end
110
+ end
data/test/test_helper.rb CHANGED
@@ -19,10 +19,19 @@ class GemTestCase < ActiveSupport::TestCase
19
19
  parallelize(workers: 1)
20
20
 
21
21
  def self.startup
22
- # Validate environment variables when tests actually start running
22
+ # Validate required environment variables
23
23
  %w[DATABASE_URL_PG DATABASE_URL_MYSQL].each do |var|
24
24
  abort "Missing required environment variable: #{var}" if ENV[var].nil? || ENV[var].empty?
25
25
  end
26
+
27
+ # Trilogy is optional (not supported on TruffleRuby)
28
+ if ENV['DATABASE_URL_TRILOGY'].nil? || ENV['DATABASE_URL_TRILOGY'].empty?
29
+ puts 'DATABASE_URL_TRILOGY not set, skipping Trilogy tests'
30
+ end
31
+ end
32
+
33
+ def self.trilogy_available?
34
+ ENV['DATABASE_URL_TRILOGY'] && !ENV['DATABASE_URL_TRILOGY'].empty?
26
35
  end
27
36
 
28
37
  # Override in test classes to clean only the tables you need
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ # Universal blocking tests - work on all adapters
6
+ module BlockingTestCases
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ setup do
11
+ @lock_name = 'test_blocking_lock'
12
+ end
13
+
14
+ test 'blocking lock acquires lock successfully' do
15
+ result = model_class.with_advisory_lock(@lock_name, blocking: true) do
16
+ 'success'
17
+ end
18
+ assert_equal('success', result)
19
+ end
20
+ end
21
+ end
22
+
23
+ class PostgreSQLBlockingTest < GemTestCase
24
+ include BlockingTestCases
25
+
26
+ def model_class
27
+ Tag
28
+ end
29
+
30
+ def setup
31
+ super
32
+ Tag.delete_all
33
+ end
34
+
35
+ test 'blocking lock waits for lock to be released' do
36
+ lock_acquired = false
37
+ thread1_finished = false
38
+
39
+ thread1 = Thread.new do
40
+ Tag.connection_pool.with_connection do
41
+ Tag.transaction do
42
+ Tag.with_advisory_lock(@lock_name, blocking: true, transaction: true) do
43
+ lock_acquired = true
44
+ sleep(0.5)
45
+ thread1_finished = true
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ sleep(0.1) until lock_acquired
52
+
53
+ thread2_result = nil
54
+ thread2 = Thread.new do
55
+ Tag.connection_pool.with_connection do
56
+ Tag.transaction do
57
+ thread2_result = Tag.with_advisory_lock(@lock_name, blocking: true, transaction: true) do
58
+ 'thread2_success'
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ thread1.join
65
+ thread2.join
66
+
67
+ assert(thread1_finished, 'Thread 1 should have finished')
68
+ assert_equal('thread2_success', thread2_result, 'Thread 2 should have acquired lock after thread 1 released it')
69
+ end
70
+
71
+ test 'blocking lock can be used with shared locks' do
72
+ thread1_result = nil
73
+ thread2_result = nil
74
+
75
+ thread1 = Thread.new do
76
+ Tag.connection_pool.with_connection do
77
+ Tag.transaction do
78
+ thread1_result = Tag.with_advisory_lock(@lock_name, blocking: true, shared: true, transaction: true) do
79
+ 'shared1'
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ thread2 = Thread.new do
86
+ Tag.connection_pool.with_connection do
87
+ Tag.transaction do
88
+ thread2_result = Tag.with_advisory_lock(@lock_name, blocking: true, shared: true, transaction: true) do
89
+ 'shared2'
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ thread1.join
96
+ thread2.join
97
+
98
+ assert_equal('shared1', thread1_result)
99
+ assert_equal('shared2', thread2_result)
100
+ end
101
+ end
102
+
103
+ class MySQLBlockingTest < GemTestCase
104
+ include BlockingTestCases
105
+
106
+ def model_class
107
+ MysqlTag
108
+ end
109
+
110
+ def setup
111
+ super
112
+ MysqlTag.delete_all
113
+ end
114
+ end
115
+
116
+ if GemTestCase.trilogy_available?
117
+ class TrilogyBlockingTest < GemTestCase
118
+ include BlockingTestCases
119
+
120
+ def model_class
121
+ TrilogyTag
122
+ end
123
+
124
+ def setup
125
+ super
126
+ TrilogyTag.delete_all
127
+ end
128
+ end
129
+ end
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
+
205
+ # Deadlock test requires non-transactional mode to work properly
206
+ class PostgreSQLDeadlockTest < GemTestCase
207
+ self.use_transactional_tests = false
208
+
209
+ def setup
210
+ super
211
+ @lock_name = 'test_blocking_lock'
212
+ Tag.delete_all
213
+ end
214
+
215
+ test 'blocking lock detects deadlocks and returns false' do
216
+ deadlock_detected = false
217
+ thread1_started = Concurrent::AtomicBoolean.new(false)
218
+ thread2_started = Concurrent::AtomicBoolean.new(false)
219
+
220
+ thread1 = Thread.new do
221
+ Tag.connection_pool.with_connection do
222
+ Tag.transaction do
223
+ Tag.with_advisory_lock('lock_a', blocking: true, transaction: true) do
224
+ thread1_started.make_true
225
+ sleep(0.1) until thread2_started.true?
226
+
227
+ result = Tag.with_advisory_lock('lock_b', blocking: true, transaction: true) do
228
+ 'should_not_reach'
229
+ end
230
+ deadlock_detected = true if result == false
231
+ end
232
+ end
233
+ rescue ActiveRecord::StatementInvalid => e
234
+ deadlock_detected = true if e.message.downcase.include?('deadlock')
235
+ end
236
+ end
237
+
238
+ thread2 = Thread.new do
239
+ Tag.connection_pool.with_connection do
240
+ Tag.transaction do
241
+ Tag.with_advisory_lock('lock_b', blocking: true, transaction: true) do
242
+ thread2_started.make_true
243
+ sleep(0.1) until thread1_started.true?
244
+
245
+ result = Tag.with_advisory_lock('lock_a', blocking: true, transaction: true) do
246
+ 'should_not_reach'
247
+ end
248
+ deadlock_detected = true if result == false
249
+ end
250
+ end
251
+ rescue ActiveRecord::StatementInvalid => e
252
+ deadlock_detected = true if e.message.downcase.include?('deadlock')
253
+ end
254
+ end
255
+
256
+ joined1 = thread1.join(10)
257
+ joined2 = thread2.join(10)
258
+
259
+ unless joined1 && joined2
260
+ thread1.kill if thread1.alive?
261
+ thread2.kill if thread2.alive?
262
+ flunk 'Deadlock detection timed out - threads did not complete within 10 seconds'
263
+ end
264
+
265
+ assert(deadlock_detected, 'Deadlock should have been detected by PostgreSQL')
266
+ end
267
+ end
@@ -40,6 +40,16 @@ class MySQLConcernTest < GemTestCase
40
40
  end
41
41
  end
42
42
 
43
+ if GemTestCase.trilogy_available?
44
+ class TrilogyConcernTest < GemTestCase
45
+ include ConcernTestCases
46
+
47
+ def model_class
48
+ TrilogyTag
49
+ end
50
+ end
51
+ end
52
+
43
53
  # This test is adapter-agnostic, so we only need to test it once
44
54
  class ActiveRecordQueryCacheTest < GemTestCase
45
55
  self.use_transactional_tests = false
@@ -200,13 +200,14 @@ class MySQLLockTest < GemTestCase
200
200
 
201
201
  begin
202
202
  # Attempt to acquire with a short timeout - should fail quickly
203
- start_time = Time.now
204
- result = model_class.with_advisory_lock(lock_name, timeout_seconds: 1) { 'success' }
205
- elapsed = Time.now - start_time
203
+ elapsed = Benchmark.realtime do
204
+ result = model_class.with_advisory_lock(lock_name, timeout_seconds: 1) { 'success' }
205
+
206
+ # Should return false and complete within reasonable time (< 3 seconds)
207
+ # If it were using Ruby polling, it would take longer
208
+ assert_not result
209
+ end
206
210
 
207
- # Should return false and complete within reasonable time (< 3 seconds)
208
- # If it were using Ruby polling, it would take longer
209
- assert_not result
210
211
  assert elapsed < 3.0, "Expected quick timeout, but took #{elapsed} seconds"
211
212
  ensure
212
213
  other_conn.query_value("SELECT RELEASE_LOCK(#{other_conn.quote(lock_keys.first)})")
@@ -214,3 +215,49 @@ class MySQLLockTest < GemTestCase
214
215
  end
215
216
  end
216
217
  end
218
+
219
+ if GemTestCase.trilogy_available?
220
+ class TrilogyLockTest < GemTestCase
221
+ include LockTestCases
222
+
223
+ def model_class
224
+ TrilogyTag
225
+ end
226
+
227
+ def setup
228
+ super
229
+ TrilogyTag.delete_all
230
+ end
231
+
232
+ test 'uses database timeout for Trilogy' do
233
+ assert model_class.connection.supports_database_timeout?
234
+ end
235
+
236
+ test 'trilogy uses native timeout instead of polling' do
237
+ # This test verifies that Trilogy bypasses Ruby-level polling
238
+ # when timeout is specified, relying on GET_LOCK's native timeout
239
+ lock_name = 'trilogy_timeout_test'
240
+
241
+ # Hold a lock in another connection - need to use the same prefixed name as the gem
242
+ other_conn = model_class.connection_pool.checkout
243
+ lock_keys = other_conn.lock_keys_for(lock_name)
244
+ other_conn.query_value("SELECT GET_LOCK(#{other_conn.quote(lock_keys.first)}, 0)")
245
+
246
+ begin
247
+ # Attempt to acquire with a short timeout - should fail quickly
248
+ elapsed = Benchmark.realtime do
249
+ result = model_class.with_advisory_lock(lock_name, timeout_seconds: 1) { 'success' }
250
+
251
+ # Should return false and complete within reasonable time (< 3 seconds)
252
+ # If it were using Ruby polling, it would take longer
253
+ assert_not result
254
+ end
255
+
256
+ assert elapsed < 3.0, "Expected quick timeout, but took #{elapsed} seconds"
257
+ ensure
258
+ other_conn.query_value("SELECT RELEASE_LOCK(#{other_conn.quote(lock_keys.first)})")
259
+ model_class.connection_pool.checkin(other_conn)
260
+ end
261
+ end
262
+ end
263
+ end
@@ -2,16 +2,90 @@
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
 
29
+ # PostgreSQL lock doesn't block MySQL
9
30
  Tag.with_advisory_lock(lock_name) do
10
- 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
11
36
  end
12
37
 
38
+ # MySQL lock doesn't block PostgreSQL
13
39
  MysqlTag.with_advisory_lock(lock_name) do
14
- 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
45
+ end
46
+ end
47
+ end
48
+
49
+ if GemTestCase.trilogy_available?
50
+ class TrilogyMultiAdapterIsolationTest < GemTestCase
51
+ include AdapterLockAssertions
52
+ test 'trilogy adapter does not overlap with postgresql or mysql' do
53
+ lock_name = 'multi-adapter-lock'
54
+
55
+ # PostgreSQL lock doesn't block Trilogy
56
+ Tag.with_advisory_lock(lock_name) do
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
62
+ end
63
+
64
+ # Trilogy lock doesn't block PostgreSQL
65
+ TrilogyTag.with_advisory_lock(lock_name) do
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
71
+ end
72
+
73
+ # MySQL lock doesn't block Trilogy
74
+ MysqlTag.with_advisory_lock(lock_name) do
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
80
+ end
81
+
82
+ TrilogyTag.with_advisory_lock(lock_name) do
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
88
+ end
15
89
  end
16
90
  end
17
91
  end
@@ -99,3 +99,13 @@ class MySQLParallelismTest < GemTestCase
99
99
  MysqlTag
100
100
  end
101
101
  end
102
+
103
+ if GemTestCase.trilogy_available?
104
+ class TrilogyParallelismTest < GemTestCase
105
+ include ParallelismTestCases
106
+
107
+ def model_class
108
+ TrilogyTag
109
+ end
110
+ end
111
+ end