with_advisory_lock 5.3.0 → 7.0.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +76 -0
  3. data/.gitignore +2 -2
  4. data/.release-please-manifest.json +1 -1
  5. data/CHANGELOG.md +39 -0
  6. data/Gemfile +31 -0
  7. data/Makefile +8 -12
  8. data/README.md +7 -35
  9. data/Rakefile +5 -2
  10. data/bin/console +11 -0
  11. data/bin/rails +15 -0
  12. data/bin/sanity +20 -0
  13. data/bin/sanity_check +86 -0
  14. data/bin/setup +8 -0
  15. data/bin/setup_test_db +59 -0
  16. data/bin/test_connections +22 -0
  17. data/docker-compose.yml +3 -4
  18. data/lib/with_advisory_lock/concern.rb +26 -19
  19. data/lib/with_advisory_lock/core_advisory.rb +110 -0
  20. data/lib/with_advisory_lock/jruby_adapter.rb +29 -0
  21. data/lib/with_advisory_lock/lock_stack_item.rb +6 -0
  22. data/lib/with_advisory_lock/mysql_advisory.rb +71 -0
  23. data/lib/with_advisory_lock/postgresql_advisory.rb +112 -0
  24. data/lib/with_advisory_lock/result.rb +14 -0
  25. data/lib/with_advisory_lock/version.rb +1 -1
  26. data/lib/with_advisory_lock.rb +38 -10
  27. data/test/dummy/Rakefile +8 -0
  28. data/test/dummy/app/controllers/application_controller.rb +7 -0
  29. data/test/dummy/app/models/application_record.rb +6 -0
  30. data/test/dummy/app/models/label.rb +4 -0
  31. data/test/dummy/app/models/mysql_label.rb +5 -0
  32. data/test/dummy/app/models/mysql_record.rb +6 -0
  33. data/test/dummy/app/models/mysql_tag.rb +10 -0
  34. data/test/dummy/app/models/mysql_tag_audit.rb +5 -0
  35. data/test/dummy/app/models/tag.rb +8 -0
  36. data/test/dummy/app/models/tag_audit.rb +4 -0
  37. data/test/dummy/config/application.rb +31 -0
  38. data/test/dummy/config/boot.rb +3 -0
  39. data/test/dummy/config/database.yml +13 -0
  40. data/test/dummy/config/environment.rb +7 -0
  41. data/test/dummy/config/routes.rb +4 -0
  42. data/test/dummy/config.ru +6 -0
  43. data/test/{test_models.rb → dummy/db/schema.rb} +2 -17
  44. data/test/dummy/db/secondary_schema.rb +15 -0
  45. data/test/dummy/lib/tasks/db.rake +40 -0
  46. data/test/sanity_check_test.rb +63 -0
  47. data/test/test_helper.rb +14 -47
  48. data/test/with_advisory_lock/concern_test.rb +58 -12
  49. data/test/with_advisory_lock/lock_test.rb +159 -73
  50. data/test/with_advisory_lock/multi_adapter_test.rb +17 -0
  51. data/test/with_advisory_lock/mysql_release_lock_test.rb +119 -0
  52. data/test/with_advisory_lock/parallelism_test.rb +63 -37
  53. data/test/with_advisory_lock/postgresql_race_condition_test.rb +118 -0
  54. data/test/with_advisory_lock/shared_test.rb +52 -57
  55. data/test/with_advisory_lock/thread_test.rb +64 -42
  56. data/test/with_advisory_lock/transaction_test.rb +55 -40
  57. data/with_advisory_lock.gemspec +25 -5
  58. metadata +55 -50
  59. data/.github/workflows/ci-mysql5.yml +0 -61
  60. data/.github/workflows/ci-mysql8.yml +0 -62
  61. data/.github/workflows/ci-postgresql.yml +0 -64
  62. data/.github/workflows/ci-sqlite3.yml +0 -54
  63. data/Appraisals +0 -45
  64. data/gemfiles/activerecord_6.1.gemfile +0 -21
  65. data/gemfiles/activerecord_7.0.gemfile +0 -21
  66. data/gemfiles/activerecord_7.1.gemfile +0 -14
  67. data/lib/with_advisory_lock/base.rb +0 -118
  68. data/lib/with_advisory_lock/database_adapter_support.rb +0 -23
  69. data/lib/with_advisory_lock/flock.rb +0 -33
  70. data/lib/with_advisory_lock/mysql.rb +0 -32
  71. data/lib/with_advisory_lock/postgresql.rb +0 -66
  72. data/test/with_advisory_lock/base_test.rb +0 -9
  73. data/test/with_advisory_lock/nesting_test.rb +0 -28
  74. data/test/with_advisory_lock/options_test.rb +0 -66
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d65989d7502660ac5cbebd51a589fc49fb76bbeae5abb13814cb79a711fdaae8
4
- data.tar.gz: 2eea1e741e2fdb100d3170ddce8c38b1d0fc59dc98f6340be51d6d5bde0591a4
3
+ metadata.gz: f594bf1ed9b20028febee227bd33eddf70f2c3deb42ae7e8a4d83d794125281d
4
+ data.tar.gz: 6882cea1e84d517bdf847b8f66b7dd6bb7b6c93de09c156b459019aab324ace0
5
5
  SHA512:
6
- metadata.gz: 8358a8783df5bcf9bd49507fc6e59d56aeaec8febd3978a4f9178950c88b254ec76925e3caa9f9f2ee18d7bc15d186512b3790dd1ba602a38fa6d1a877570887
7
- data.tar.gz: d179cc20743f15a671ad822fd21e04e808336382d2eed318439e7b9c5ae9283ff4abf0576e5b0c274946863033f403d27280dfec84b3e3e4ff20f832ca62bcb5
6
+ metadata.gz: a89eff2b9fccadd2f64ac7467c9daf19156fbac5196358b9ce7089c2d743c353e4fbb8b993d2c0440b0f253c78a56620a603cd9d41dd0e3f027e02b5f901f72c
7
+ data.tar.gz: 02b5903b081175e8f7ce07450738fd88d8f0f92dfc52fbb6901e9738aa90312313c3313c70ccfcdab998aa799399a173215edf913145b72d56958042681fb4e6
@@ -0,0 +1,76 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - master
7
+
8
+ concurrency:
9
+ group: ci-${{ github.head_ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ minitest:
14
+ runs-on: ubuntu-latest
15
+ name: CI Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
16
+ services:
17
+ postgres:
18
+ image: 'postgres:17-alpine'
19
+ ports:
20
+ - '5432'
21
+ env:
22
+ POSTGRES_USER: with_advisory
23
+ POSTGRES_PASSWORD: with_advisory_pass
24
+ POSTGRES_DB: with_advisory_lock_test
25
+ options: >-
26
+ --health-cmd pg_isready
27
+ --health-interval 10s
28
+ --health-timeout 5s
29
+ --health-retries 5
30
+ mysql:
31
+ image: mysql/mysql-server
32
+ ports:
33
+ - 3306
34
+ env:
35
+ MYSQL_USER: with_advisory
36
+ MYSQL_PASSWORD: with_advisory_pass
37
+ MYSQL_DATABASE: with_advisory_lock_test
38
+ MYSQL_ROOT_HOST: '%'
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ ruby:
43
+ - '3.3'
44
+ - '3.4'
45
+ - 'truffleruby'
46
+ rails:
47
+ - 7.2
48
+ - "8.0"
49
+ env:
50
+ ACTIVERECORD_VERSION: ${{ matrix.rails }}
51
+ RAILS_ENV: test
52
+ steps:
53
+ - name: Checkout
54
+ uses: actions/checkout@v4
55
+
56
+ - name: Setup Ruby
57
+ uses: ruby/setup-ruby@v1
58
+ with:
59
+ ruby-version: ${{ matrix.ruby }}
60
+ bundler-cache: true
61
+ rubygems: latest
62
+
63
+ - name: Setup test databases
64
+ env:
65
+ DATABASE_URL_PG: postgres://with_advisory:with_advisory_pass@localhost:${{ job.services.postgres.ports[5432] }}/with_advisory_lock_test
66
+ DATABASE_URL_MYSQL: mysql2://with_advisory:with_advisory_pass@127.0.0.1:${{ job.services.mysql.ports[3306] }}/with_advisory_lock_test
67
+ run: |
68
+ cd test/dummy
69
+ bundle exec rake db:test:prepare
70
+
71
+ - name: Test
72
+ env:
73
+ DATABASE_URL_PG: postgres://with_advisory:with_advisory_pass@localhost:${{ job.services.postgres.ports[5432] }}/with_advisory_lock_test
74
+ DATABASE_URL_MYSQL: mysql2://with_advisory:with_advisory_pass@127.0.0.1:${{ job.services.mysql.ports[3306] }}/with_advisory_lock_test
75
+ WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
76
+ run: bin/rails test
data/.gitignore CHANGED
@@ -18,5 +18,5 @@ test/tmp
18
18
  test/version_tmp
19
19
  tmp
20
20
  *.iml
21
- test/sqlite.db
22
- .env
21
+ .env
22
+ test/dummy/log/
@@ -1 +1 @@
1
- {".":"5.3.0"}
1
+ {".":"7.0.1"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  ## Changelog
2
2
 
3
+ ## [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)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * 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)
9
+
10
+ ## [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)
11
+
12
+
13
+ ### ⚠ BREAKING CHANGES
14
+
15
+ * require Rails 7.2+ as minimum version
16
+
17
+ ### Features
18
+
19
+ * fire Ruby from its second job checking locks and let PostgreSQL do what it's paid for ([#124](https://github.com/ClosureTree/with_advisory_lock/issues/124)) ([f7f8dbc](https://github.com/ClosureTree/with_advisory_lock/commit/f7f8dbcd69842a358d0d70227bcc52ba3183c098))
20
+ * handle connection disconnection gracefully ([1944e98](https://github.com/ClosureTree/with_advisory_lock/commit/1944e98877917e234bfe597f9358c0b74643a045))
21
+ * handle connection disconnection gracefully ([77046a9](https://github.com/ClosureTree/with_advisory_lock/commit/77046a94c7504f77a59fae6fbcd75e73ed41bf23))
22
+ * implement MySQL native timeout support ([#123](https://github.com/ClosureTree/with_advisory_lock/issues/123)) ([387dedd](https://github.com/ClosureTree/with_advisory_lock/commit/387dedd133c897f7a3da13ed2ebbd9223b81317d))
23
+ * require Rails 7.2+ as minimum version ([d4e7826](https://github.com/ClosureTree/with_advisory_lock/commit/d4e7826ddc216c103cd666674068cb7f512fc32d))
24
+ * validate transaction-level locks require active transaction ([#122](https://github.com/ClosureTree/with_advisory_lock/issues/122)) ([e4bc6c1](https://github.com/ClosureTree/with_advisory_lock/commit/e4bc6c10666e02c560f18629df0106c39bb85e19))
25
+
26
+ ## [6.0.0](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v5.3.0...with_advisory_lock/v6.0.0) (2025-05-28)
27
+
28
+
29
+ ### ⚠ BREAKING CHANGES
30
+
31
+ * Remove private APIs (Base, DatabaseAdapterSupport). Add full mixed adapter support for PostgreSQL/MySQL in same app. Add JRuby compatibility.
32
+ * drop support for sqlite3
33
+ * drop legacy version of ruby/rails ([#113](https://github.com/ClosureTree/with_advisory_lock/issues/113))
34
+
35
+ ### Features
36
+
37
+ * drop legacy version of ruby/rails ([#113](https://github.com/ClosureTree/with_advisory_lock/issues/113)) ([26fd427](https://github.com/ClosureTree/with_advisory_lock/commit/26fd4278f9fa155974e6f86df7cd92dd2b7d9154))
38
+ * drop support for sqlite3 ([26fd427](https://github.com/ClosureTree/with_advisory_lock/commit/26fd4278f9fa155974e6f86df7cd92dd2b7d9154))
39
+ * move to rails dummy app to test multidb setup ([#115](https://github.com/ClosureTree/with_advisory_lock/issues/115)) ([71a3431](https://github.com/ClosureTree/with_advisory_lock/commit/71a34316b365a0f3be0e8a046db14289e69efc9c))
40
+ * support of multidb ([#116](https://github.com/ClosureTree/with_advisory_lock/issues/116)) ([935e7e5](https://github.com/ClosureTree/with_advisory_lock/commit/935e7e5fb05dad2eba034745d2ef49e11c163f7d))
41
+
3
42
  ## [5.3.0](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v5.2.0...with_advisory_lock/v5.3.0) (2025-04-25)
4
43
 
5
44
 
data/Gemfile CHANGED
@@ -1,3 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
6
+
7
+ gem 'rake'
8
+
9
+ # Gems that will be removed from default gems in Ruby 3.5.0
10
+ gem 'benchmark'
11
+ gem 'logger'
12
+ gem 'ostruct'
13
+
14
+ activerecord_version = ENV.fetch('ACTIVERECORD_VERSION', '7.2')
15
+
16
+ gem 'activerecord', "~> #{activerecord_version}.0"
17
+
18
+ gem 'dotenv'
19
+ gem 'railties'
20
+
21
+ platforms :ruby do
22
+ gem 'mysql2'
23
+ gem 'pg'
24
+ gem 'sqlite3'
25
+ gem 'trilogy'
26
+ end
27
+
28
+ platforms :jruby do
29
+ # JRuby JDBC adapters support Rails 7.2+
30
+ if activerecord_version >= '7.2'
31
+ gem 'activerecord-jdbcmysql-adapter', '~> 72.0'
32
+ gem 'activerecord-jdbcpostgresql-adapter', '~> 72.0'
33
+ end
34
+ end
data/Makefile CHANGED
@@ -1,14 +1,10 @@
1
- .PHONY: test-pg test-mysql
1
+ .PHONY: test
2
2
 
3
- test-pg:
4
- docker compose up -d pg
5
- sleep 10 # give some time for the service to start
6
- DATABASE_URL=postgres://with_advisory:with_advisory_pass@localhost/with_advisory_lock_test appraisal rake test
3
+ test: setup-db
4
+ bin/rails test
7
5
 
8
- test-mysql:
9
- docker compose up -d mysql
10
- sleep 10 # give some time for the service to start
11
- DATABASE_URL=mysql2://with_advisory:with_advisory_pass@0.0.0.0:3306/with_advisory_lock_test appraisal rake test
12
-
13
-
14
- test: test-pg test-mysql
6
+ setup-db:
7
+ docker compose up -d
8
+ sleep 2
9
+ bundle
10
+ bin/setup_test_db
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # with_advisory_lock
2
2
 
3
- Adds advisory locking (mutexes) to ActiveRecord 6.0+, with ruby 2.7+, jruby or truffleruby, when used with
3
+ Adds advisory locking (mutexes) to ActiveRecord 7.2+, with ruby 3.3+, jruby or truffleruby, when used with
4
4
  [MySQL](https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_get-lock)
5
5
  or
6
6
  [PostgreSQL](https://www.postgresql.org/docs/current/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS).
7
- SQLite resorts to file locking.
7
+
8
+ **Note:** SQLite support has been removed. For single-node SQLite deployments,
9
+ consider using a Ruby mutex instead. Support for MySQL 5.7 has also been
10
+ dropped; please use MySQL 8 or PostgreSQL.
8
11
 
9
12
  [![Gem Version](https://badge.fury.io/rb/with_advisory_lock.svg)](https://badge.fury.io/rb/with_advisory_lock)
10
13
  [![CI](https://github.com/ClosureTree/with_advisory_lock/actions/workflows/ci.yml/badge.svg)](https://github.com/ClosureTree/with_advisory_lock/actions/workflows/ci.yml)
@@ -13,7 +16,7 @@ SQLite resorts to file locking.
13
16
 
14
17
  An advisory lock is a [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion)
15
18
  used to ensure no two processes run some process at the same time. When the
16
- advisory lock is powered by your database server, as long as it isn't SQLite,
19
+ advisory lock is powered by your database server,
17
20
  your mutex spans hosts.
18
21
 
19
22
  ## Usage
@@ -134,7 +137,7 @@ row-level locks prevent concurrent modification to a given model.
134
137
 
135
138
  **If you're building a
136
139
  [CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete)
137
- application, this will be 2.4, 2.5 and your most commonly used lock.**
140
+ application, this will be 2.4, 2.5 and your most commonly used lock.**
138
141
 
139
142
  ### Table-level locks
140
143
 
@@ -152,38 +155,7 @@ Advisory locks with MySQL and PostgreSQL ignore database transaction boundaries.
152
155
 
153
156
  You will want to wrap your block within a transaction to ensure consistency.
154
157
 
155
- ### MySQL < 5.7.5 doesn't support nesting
156
-
157
- With MySQL < 5.7.5, if you ask for a _different_ advisory lock within
158
- a `with_advisory_lock` block, you will be releasing the parent lock (!!!). A
159
- `NestedAdvisoryLockError`will be raised in this case. If you ask for the same
160
- lock name, `with_advisory_lock` won't ask for the lock again, and the block
161
- given will be yielded to.
162
-
163
- This is not an issue in MySQL >= 5.7.5, and no error will be raised for nested
164
- lock usage. You can override this by passing `force_nested_lock_support: true`
165
- or `force_nested_lock_support: false` to the `with_advisory_lock` options.
166
-
167
158
  ### Is clustered MySQL supported?
168
159
 
169
160
  [No.](https://github.com/ClosureTree/with_advisory_lock/issues/16)
170
161
 
171
- ### There are many `lock-*` files in my project directory after test runs
172
-
173
- This is expected if you aren't using MySQL or Postgresql for your tests.
174
- See [issue 3](https://github.com/ClosureTree/with_advisory_lock/issues/3).
175
-
176
- SQLite doesn't have advisory locks, so we resort to file locking, which will
177
- only work if the `FLOCK_DIR` is set consistently for all ruby processes.
178
-
179
- In your `spec_helper.rb` or `minitest_helper.rb`, add a `before` and `after` block:
180
-
181
- ```ruby
182
- before do
183
- ENV['FLOCK_DIR'] = Dir.mktmpdir
184
- end
185
-
186
- after do
187
- FileUtils.remove_entry_secure ENV['FLOCK_DIR']
188
- end
189
- ```
data/Rakefile CHANGED
@@ -1,4 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
2
4
 
3
5
  require 'yard'
4
6
  YARD::Rake::YardocTask.new do |t|
@@ -14,4 +16,5 @@ Rake::TestTask.new do |t|
14
16
  t.verbose = true
15
17
  end
16
18
 
17
- task :default => :test
19
+
20
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'no_fly_list'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require 'irb'
11
+ IRB.start(__FILE__)
data/bin/rails ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This command will automatically be run when you run "rails" with Rails gems
5
+ # installed from the root of your application.
6
+
7
+ ENGINE_ROOT = File.expand_path('..', __dir__)
8
+ APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
9
+
10
+ # Set up gems listed in the Gemfile.
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
13
+
14
+ require 'rails/all'
15
+ require 'rails/engine/commands'
data/bin/sanity ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ check_port() {
5
+ local port="$1"
6
+ local name="$2"
7
+ if nc -z localhost "$port" >/dev/null 2>&1; then
8
+ echo "$name running on port $port"
9
+ else
10
+ echo "ERROR: $name is not running on port $port" >&2
11
+ return 1
12
+ fi
13
+ }
14
+
15
+ main() {
16
+ check_port 5433 "Postgresql"
17
+ check_port 3366 "Mysql"
18
+ }
19
+
20
+ main "$@"
data/bin/sanity_check ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ ENV['RAILS_ENV'] = 'test'
5
+ ENV['DATABASE_URL_PG'] ||= 'postgres://with_advisory:with_advisory_pass@localhost:5433/with_advisory_lock_test'
6
+ ENV['DATABASE_URL_MYSQL'] ||= 'mysql2://with_advisory:with_advisory_pass@0.0.0.0:3366/with_advisory_lock_test'
7
+
8
+ require_relative '../test/dummy/config/environment'
9
+
10
+ puts '=' * 80
11
+ puts 'WITH_ADVISORY_LOCK SANITY CHECK'
12
+ puts '=' * 80
13
+ puts
14
+
15
+ # Check Rails environment
16
+ puts "Rails Environment: #{Rails.env}"
17
+ puts "Rails Root: #{Rails.root}"
18
+ puts
19
+
20
+ # Check PostgreSQL connection
21
+ puts 'PostgreSQL Connection (ApplicationRecord):'
22
+ begin
23
+ ApplicationRecord.connection.execute('SELECT 1')
24
+ puts ' ✓ Connected to PostgreSQL'
25
+ puts " Database: #{ApplicationRecord.connection.current_database}"
26
+ puts " Adapter: #{ApplicationRecord.connection.adapter_name}"
27
+ puts " Tables: #{ApplicationRecord.connection.tables.sort.join(', ')}"
28
+
29
+ # Test creating a record
30
+ tag = Tag.create!(name: "test-pg-#{Time.now.to_i}")
31
+ puts " ✓ Created Tag record with id: #{tag.id}"
32
+ tag.destroy
33
+ puts ' ✓ Deleted Tag record'
34
+ rescue StandardError => e
35
+ puts " ✗ ERROR: #{e.message}"
36
+ puts " #{e.backtrace.first}"
37
+ end
38
+ puts
39
+
40
+ # Check MySQL connection
41
+ puts 'MySQL Connection (MysqlRecord):'
42
+ begin
43
+ MysqlRecord.connection.execute('SELECT 1')
44
+ puts ' ✓ Connected to MySQL'
45
+ puts " Database: #{MysqlRecord.connection.current_database}"
46
+ puts " Adapter: #{MysqlRecord.connection.adapter_name}"
47
+ puts " Tables: #{MysqlRecord.connection.tables.sort.join(', ')}"
48
+
49
+ # Test creating a record
50
+ mysql_tag = MysqlTag.create!(name: "test-mysql-#{Time.now.to_i}")
51
+ puts " ✓ Created MysqlTag record with id: #{mysql_tag.id}"
52
+ mysql_tag.destroy
53
+ puts ' ✓ Deleted MysqlTag record'
54
+ rescue StandardError => e
55
+ puts " ✗ ERROR: #{e.message}"
56
+ puts " #{e.backtrace.first}"
57
+ end
58
+ puts
59
+
60
+ # Check model associations
61
+ puts 'Model Configuration:'
62
+ puts ' PostgreSQL Models:'
63
+ puts " - Tag -> #{Tag.connection.adapter_name}"
64
+ puts " - TagAudit -> #{TagAudit.connection.adapter_name}"
65
+ puts " - Label -> #{Label.connection.adapter_name}"
66
+ puts ' MySQL Models:'
67
+ puts " - MysqlTag -> #{MysqlTag.connection.adapter_name}"
68
+ puts " - MysqlTagAudit -> #{MysqlTagAudit.connection.adapter_name}"
69
+ puts " - MysqlLabel -> #{MysqlLabel.connection.adapter_name}"
70
+ puts
71
+
72
+ # Check if WithAdvisoryLock is loaded
73
+ puts 'WithAdvisoryLock Status:'
74
+ puts " Module loaded: #{defined?(WithAdvisoryLock) ? 'Yes' : 'No'}"
75
+ puts " Concern loaded: #{defined?(WithAdvisoryLock::Concern) ? 'Yes' : 'No'}"
76
+ puts " PostgreSQL adapter loaded: #{defined?(WithAdvisoryLock::PostgreSQL) ? 'Yes' : 'No'}"
77
+ puts " MySQL adapter loaded: #{defined?(WithAdvisoryLock::MySQL) ? 'Yes' : 'No'}"
78
+
79
+ # Check if models have advisory lock methods
80
+ puts "\nModel Methods:"
81
+ puts " Tag.with_advisory_lock available: #{Tag.respond_to?(:with_advisory_lock)}"
82
+ puts " MysqlTag.with_advisory_lock available: #{MysqlTag.respond_to?(:with_advisory_lock)}"
83
+
84
+ puts "\n#{'=' * 80}"
85
+ puts 'SANITY CHECK COMPLETE'
86
+ puts '=' * 80
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+ bin/rails db:setup
8
+ bin/rails db:migrate
data/bin/setup_test_db ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'active_record'
6
+
7
+ # Setup PostgreSQL database
8
+ puts 'Setting up PostgreSQL test database...'
9
+ ActiveRecord::Base.establish_connection(
10
+ adapter: 'postgresql',
11
+ host: 'localhost',
12
+ port: 5433,
13
+ database: 'with_advisory_lock_test',
14
+ username: 'with_advisory',
15
+ password: 'with_advisory_pass'
16
+ )
17
+
18
+ ActiveRecord::Schema.define(version: 1) do
19
+ create_table 'tags', force: true do |t|
20
+ t.string 'name'
21
+ end
22
+
23
+ create_table 'tag_audits', id: false, force: true do |t|
24
+ t.string 'tag_name'
25
+ end
26
+
27
+ create_table 'labels', id: false, force: true do |t|
28
+ t.string 'name'
29
+ end
30
+ end
31
+ puts 'PostgreSQL tables created!'
32
+
33
+ # Setup MySQL database
34
+ puts "\nSetting up MySQL test database..."
35
+ ActiveRecord::Base.establish_connection(
36
+ adapter: 'mysql2',
37
+ host: '127.0.0.1',
38
+ port: 3366,
39
+ database: 'with_advisory_lock_test',
40
+ username: 'with_advisory',
41
+ password: 'with_advisory_pass'
42
+ )
43
+
44
+ ActiveRecord::Schema.define(version: 1) do
45
+ create_table 'mysql_tags', force: true do |t|
46
+ t.string 'name'
47
+ end
48
+
49
+ create_table 'mysql_tag_audits', id: false, force: true do |t|
50
+ t.string 'tag_name'
51
+ end
52
+
53
+ create_table 'mysql_labels', id: false, force: true do |t|
54
+ t.string 'name'
55
+ end
56
+ end
57
+ puts 'MySQL tables created!'
58
+
59
+ puts "\nTest databases setup complete!"
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ ENV['RAILS_ENV'] = 'test'
5
+ ENV['DATABASE_URL_PG'] ||= 'postgres://with_advisory:with_advisory_pass@localhost:5433/with_advisory_lock_test'
6
+ ENV['DATABASE_URL_MYSQL'] ||= 'mysql2://with_advisory:with_advisory_pass@0.0.0.0:3366/with_advisory_lock_test'
7
+
8
+ require_relative '../test/dummy/config/environment'
9
+
10
+ puts 'Testing database connections...'
11
+
12
+ puts "\nPostgreSQL (ApplicationRecord):"
13
+ puts " Connected: #{ApplicationRecord.connected?}"
14
+ puts " Tables: #{ApplicationRecord.connection.tables.sort.join(', ')}"
15
+
16
+ puts "\nMySQL (MysqlRecord):"
17
+ puts " Connected: #{MysqlRecord.connected?}"
18
+ puts " Tables: #{MysqlRecord.connection.tables.sort.join(', ')}"
19
+
20
+ puts "\nModel connections:"
21
+ puts " Tag uses: #{Tag.connection.adapter_name}"
22
+ puts " MysqlTag uses: #{MysqlTag.connection.adapter_name}"
data/docker-compose.yml CHANGED
@@ -1,13 +1,12 @@
1
- version: "3.9"
2
1
  services:
3
2
  pg:
4
- image: postgres:16
3
+ image: postgres:17-alpine
5
4
  environment:
6
5
  POSTGRES_USER: with_advisory
7
6
  POSTGRES_PASSWORD: with_advisory_pass
8
7
  POSTGRES_DB: with_advisory_lock_test
9
8
  ports:
10
- - "5432:5432"
9
+ - "5433:5432"
11
10
  mysql:
12
11
  image: mysql:8
13
12
  environment:
@@ -17,4 +16,4 @@ services:
17
16
  MYSQL_RANDOM_ROOT_PASSWORD: "yes"
18
17
  MYSQL_ROOT_HOST: '%'
19
18
  ports:
20
- - "3306:3306"
19
+ - "3366:3306"
@@ -19,34 +19,41 @@ module WithAdvisoryLock
19
19
  end
20
20
 
21
21
  def with_advisory_lock_result(lock_name, options = {}, &block)
22
- impl = impl_class.new(connection, lock_name, options)
23
- impl.with_advisory_lock_if_needed(&block)
22
+ with_connection do |conn|
23
+ conn.with_advisory_lock_if_needed(lock_name, options, &block)
24
+ end
24
25
  end
25
26
 
26
27
  def advisory_lock_exists?(lock_name)
27
- impl = impl_class.new(connection, lock_name, 0)
28
- impl.already_locked? || !impl.yield_with_lock.lock_was_acquired?
28
+ with_connection do |conn|
29
+ lock_str = "#{ENV.fetch(CoreAdvisory::LOCK_PREFIX_ENV, nil)}#{lock_name}"
30
+ lock_stack_item = LockStackItem.new(lock_str, false)
31
+
32
+ if conn.advisory_lock_stack.include?(lock_stack_item)
33
+ true
34
+ else
35
+ # For PostgreSQL, try non-blocking query first to avoid race conditions
36
+ if conn.respond_to?(:advisory_lock_exists_for?)
37
+ query_result = conn.advisory_lock_exists_for?(lock_name)
38
+ return query_result unless query_result.nil?
39
+ end
40
+
41
+ # Fall back to the original implementation
42
+ result = conn.with_advisory_lock_if_needed(lock_name, { timeout_seconds: 0 })
43
+ !result.lock_was_acquired?
44
+ end
45
+ end
29
46
  end
30
47
 
31
48
  def current_advisory_lock
32
- lock_stack_key = WithAdvisoryLock::Base.lock_stack.first
33
- lock_stack_key && lock_stack_key[0]
49
+ with_connection do |conn|
50
+ conn.advisory_lock_stack.first&.name
51
+ end
34
52
  end
35
53
 
36
54
  def current_advisory_locks
37
- WithAdvisoryLock::Base.lock_stack.map(&:name)
38
- end
39
-
40
- private
41
-
42
- def impl_class
43
- adapter = WithAdvisoryLock::DatabaseAdapterSupport.new(connection)
44
- if adapter.postgresql?
45
- WithAdvisoryLock::PostgreSQL
46
- elsif adapter.mysql?
47
- WithAdvisoryLock::MySQL
48
- else
49
- WithAdvisoryLock::Flock
55
+ with_connection do |conn|
56
+ conn.advisory_lock_stack.map(&:name)
50
57
  end
51
58
  end
52
59
  end