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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +76 -0
- data/.gitignore +2 -2
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +39 -0
- data/Gemfile +31 -0
- data/Makefile +8 -12
- data/README.md +7 -35
- data/Rakefile +5 -2
- data/bin/console +11 -0
- data/bin/rails +15 -0
- data/bin/sanity +20 -0
- data/bin/sanity_check +86 -0
- data/bin/setup +8 -0
- data/bin/setup_test_db +59 -0
- data/bin/test_connections +22 -0
- data/docker-compose.yml +3 -4
- data/lib/with_advisory_lock/concern.rb +26 -19
- data/lib/with_advisory_lock/core_advisory.rb +110 -0
- data/lib/with_advisory_lock/jruby_adapter.rb +29 -0
- data/lib/with_advisory_lock/lock_stack_item.rb +6 -0
- data/lib/with_advisory_lock/mysql_advisory.rb +71 -0
- data/lib/with_advisory_lock/postgresql_advisory.rb +112 -0
- data/lib/with_advisory_lock/result.rb +14 -0
- data/lib/with_advisory_lock/version.rb +1 -1
- data/lib/with_advisory_lock.rb +38 -10
- data/test/dummy/Rakefile +8 -0
- data/test/dummy/app/controllers/application_controller.rb +7 -0
- data/test/dummy/app/models/application_record.rb +6 -0
- data/test/dummy/app/models/label.rb +4 -0
- data/test/dummy/app/models/mysql_label.rb +5 -0
- data/test/dummy/app/models/mysql_record.rb +6 -0
- data/test/dummy/app/models/mysql_tag.rb +10 -0
- data/test/dummy/app/models/mysql_tag_audit.rb +5 -0
- data/test/dummy/app/models/tag.rb +8 -0
- data/test/dummy/app/models/tag_audit.rb +4 -0
- data/test/dummy/config/application.rb +31 -0
- data/test/dummy/config/boot.rb +3 -0
- data/test/dummy/config/database.yml +13 -0
- data/test/dummy/config/environment.rb +7 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config.ru +6 -0
- data/test/{test_models.rb → dummy/db/schema.rb} +2 -17
- data/test/dummy/db/secondary_schema.rb +15 -0
- data/test/dummy/lib/tasks/db.rake +40 -0
- data/test/sanity_check_test.rb +63 -0
- data/test/test_helper.rb +14 -47
- data/test/with_advisory_lock/concern_test.rb +58 -12
- data/test/with_advisory_lock/lock_test.rb +159 -73
- data/test/with_advisory_lock/multi_adapter_test.rb +17 -0
- data/test/with_advisory_lock/mysql_release_lock_test.rb +119 -0
- data/test/with_advisory_lock/parallelism_test.rb +63 -37
- data/test/with_advisory_lock/postgresql_race_condition_test.rb +118 -0
- data/test/with_advisory_lock/shared_test.rb +52 -57
- data/test/with_advisory_lock/thread_test.rb +64 -42
- data/test/with_advisory_lock/transaction_test.rb +55 -40
- data/with_advisory_lock.gemspec +25 -5
- metadata +55 -50
- data/.github/workflows/ci-mysql5.yml +0 -61
- data/.github/workflows/ci-mysql8.yml +0 -62
- data/.github/workflows/ci-postgresql.yml +0 -64
- data/.github/workflows/ci-sqlite3.yml +0 -54
- data/Appraisals +0 -45
- data/gemfiles/activerecord_6.1.gemfile +0 -21
- data/gemfiles/activerecord_7.0.gemfile +0 -21
- data/gemfiles/activerecord_7.1.gemfile +0 -14
- data/lib/with_advisory_lock/base.rb +0 -118
- data/lib/with_advisory_lock/database_adapter_support.rb +0 -23
- data/lib/with_advisory_lock/flock.rb +0 -33
- data/lib/with_advisory_lock/mysql.rb +0 -32
- data/lib/with_advisory_lock/postgresql.rb +0 -66
- data/test/with_advisory_lock/base_test.rb +0 -9
- data/test/with_advisory_lock/nesting_test.rb +0 -28
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f594bf1ed9b20028febee227bd33eddf70f2c3deb42ae7e8a4d83d794125281d
|
4
|
+
data.tar.gz: 6882cea1e84d517bdf847b8f66b7dd6bb7b6c93de09c156b459019aab324ace0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
@@ -1 +1 @@
|
|
1
|
-
{".":"
|
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
|
1
|
+
.PHONY: test
|
2
2
|
|
3
|
-
test-
|
4
|
-
|
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
|
-
|
9
|
-
docker compose up -d
|
10
|
-
sleep
|
11
|
-
|
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
|
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
|
-
|
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
|
[](https://badge.fury.io/rb/with_advisory_lock)
|
10
13
|
[](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,
|
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
|
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
|
-
|
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
|
-
|
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
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:
|
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
|
-
- "
|
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
|
-
- "
|
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
|
-
|
23
|
-
|
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
|
-
|
28
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
38
|
-
|
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
|