with_advisory_lock 5.1.0 → 5.3.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 +4 -4
- data/.github/workflows/ci-mysql5.yml +61 -0
- data/.github/workflows/ci-mysql8.yml +62 -0
- data/.github/workflows/ci-postgresql.yml +64 -0
- data/.github/workflows/ci-sqlite3.yml +54 -0
- data/.github/workflows/release.yml +1 -4
- data/.release-please-manifest.json +1 -1
- data/.ruby-version +2 -0
- data/.tool-versions +1 -1
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +4 -4
- data/Makefile +14 -0
- data/docker-compose.yml +20 -0
- data/lib/with_advisory_lock/base.rb +2 -2
- data/lib/with_advisory_lock/concern.rb +4 -0
- data/lib/with_advisory_lock/database_adapter_support.rb +5 -8
- data/lib/with_advisory_lock/mysql.rb +8 -3
- data/lib/with_advisory_lock/postgresql.rb +38 -15
- data/lib/with_advisory_lock/version.rb +1 -1
- data/lib/with_advisory_lock.rb +1 -0
- data/test/test_helper.rb +20 -6
- data/test/test_models.rb +8 -4
- data/test/with_advisory_lock/base_test.rb +9 -0
- data/test/{concern_test.rb → with_advisory_lock/concern_test.rb} +2 -2
- data/test/{lock_test.rb → with_advisory_lock/lock_test.rb} +31 -0
- data/test/{parallelism_test.rb → with_advisory_lock/parallelism_test.rb} +3 -3
- data/test/{shared_test.rb → with_advisory_lock/shared_test.rb} +2 -2
- data/test/{thread_test.rb → with_advisory_lock/thread_test.rb} +2 -2
- data/test/{transaction_test.rb → with_advisory_lock/transaction_test.rb} +1 -1
- data/with_advisory_lock.gemspec +1 -1
- metadata +29 -24
- data/.github/workflows/ci.yml +0 -80
- /data/test/{nesting_test.rb → with_advisory_lock/nesting_test.rb} +0 -0
- /data/test/{options_test.rb → with_advisory_lock/options_test.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d65989d7502660ac5cbebd51a589fc49fb76bbeae5abb13814cb79a711fdaae8
|
4
|
+
data.tar.gz: 2eea1e741e2fdb100d3170ddce8c38b1d0fc59dc98f6340be51d6d5bde0591a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8358a8783df5bcf9bd49507fc6e59d56aeaec8febd3978a4f9178950c88b254ec76925e3caa9f9f2ee18d7bc15d186512b3790dd1ba602a38fa6d1a877570887
|
7
|
+
data.tar.gz: d179cc20743f15a671ad822fd21e04e808336382d2eed318439e7b9c5ae9283ff4abf0576e5b0c274946863033f403d27280dfec84b3e3e4ff20f832ca62bcb5
|
@@ -0,0 +1,61 @@
|
|
1
|
+
name: CI Mysql 5.7
|
2
|
+
on:
|
3
|
+
pull_request:
|
4
|
+
branches:
|
5
|
+
- master
|
6
|
+
concurrency:
|
7
|
+
group: ci-mysql5-${{ github.head_ref }}
|
8
|
+
cancel-in-progress: true
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
minitest:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
name: CI Mysql 5.7 Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / Adapter ${{ matrix.adapter }}
|
14
|
+
services:
|
15
|
+
mysql:
|
16
|
+
image: mysql/mysql-server:5.7
|
17
|
+
ports:
|
18
|
+
- 3306
|
19
|
+
env:
|
20
|
+
MYSQL_USER: with_advisory
|
21
|
+
MYSQL_PASSWORD: with_advisory_pass
|
22
|
+
MYSQL_DATABASE: with_advisory_lock_test
|
23
|
+
MYSQL_ROOT_HOST: '%'
|
24
|
+
strategy:
|
25
|
+
fail-fast: false
|
26
|
+
matrix:
|
27
|
+
ruby:
|
28
|
+
# - '3.2'
|
29
|
+
# - '3.1'
|
30
|
+
# - '3.0'
|
31
|
+
# - '2.7'
|
32
|
+
- '3.3'
|
33
|
+
- 'truffleruby'
|
34
|
+
rails:
|
35
|
+
- 7.1
|
36
|
+
- "7.0"
|
37
|
+
- 6.1
|
38
|
+
adapter:
|
39
|
+
- mysql2
|
40
|
+
- trilogy
|
41
|
+
include:
|
42
|
+
- ruby: jruby
|
43
|
+
rails: 6.1
|
44
|
+
adapter: jdbcmysql
|
45
|
+
steps:
|
46
|
+
- name: Checkout
|
47
|
+
uses: actions/checkout@v4
|
48
|
+
- name: Setup Ruby
|
49
|
+
uses: ruby/setup-ruby@v1
|
50
|
+
with:
|
51
|
+
ruby-version: ${{ matrix.ruby }}
|
52
|
+
bundler-cache: true
|
53
|
+
rubygems: latest
|
54
|
+
env:
|
55
|
+
BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.rails }}.gemfile
|
56
|
+
- name: Test
|
57
|
+
env:
|
58
|
+
BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.rails }}.gemfile
|
59
|
+
DATABASE_URL: ${{ matrix.adapter }}://with_advisory:with_advisory_pass@0:${{ job.services.mysql.ports[3306] }}/with_advisory_lock_test
|
60
|
+
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
|
61
|
+
run: bundle exec rake
|
@@ -0,0 +1,62 @@
|
|
1
|
+
name: CI Mysql 8.0
|
2
|
+
on:
|
3
|
+
pull_request:
|
4
|
+
branches:
|
5
|
+
- master
|
6
|
+
|
7
|
+
concurrency:
|
8
|
+
group: ci-mysql8-${{ github.head_ref }}
|
9
|
+
cancel-in-progress: true
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
minitest:
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
name: CI Mysql 8.0 Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / Adapter ${{ matrix.adapter }}
|
15
|
+
services:
|
16
|
+
mysql:
|
17
|
+
image: mysql/mysql-server
|
18
|
+
ports:
|
19
|
+
- 3306
|
20
|
+
env:
|
21
|
+
MYSQL_USER: with_advisory
|
22
|
+
MYSQL_PASSWORD: with_advisory_pass
|
23
|
+
MYSQL_DATABASE: with_advisory_lock_test
|
24
|
+
MYSQL_ROOT_HOST: '%'
|
25
|
+
strategy:
|
26
|
+
fail-fast: false
|
27
|
+
matrix:
|
28
|
+
ruby:
|
29
|
+
# - '3.2'
|
30
|
+
# - '3.1'
|
31
|
+
# - '3.0'
|
32
|
+
# - '2.7'
|
33
|
+
- '3.3'
|
34
|
+
- 'truffleruby'
|
35
|
+
rails:
|
36
|
+
- 7.1
|
37
|
+
- "7.0"
|
38
|
+
- 6.1
|
39
|
+
adapter:
|
40
|
+
- mysql2
|
41
|
+
# - trilogy://with_advisory:with_advisory_pass@0/with_advisory_lock_test Trilogy is not supported by mysql 8 with new encryption
|
42
|
+
include:
|
43
|
+
- ruby: jruby
|
44
|
+
rails: 6.1
|
45
|
+
adapter: jdbcmysql
|
46
|
+
steps:
|
47
|
+
- name: Checkout
|
48
|
+
uses: actions/checkout@v4
|
49
|
+
- name: Setup Ruby
|
50
|
+
uses: ruby/setup-ruby@v1
|
51
|
+
with:
|
52
|
+
ruby-version: ${{ matrix.ruby }}
|
53
|
+
bundler-cache: true
|
54
|
+
rubygems: latest
|
55
|
+
env:
|
56
|
+
BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.rails }}.gemfile
|
57
|
+
- name: Test
|
58
|
+
env:
|
59
|
+
BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.rails }}.gemfile
|
60
|
+
DATABASE_URL: ${{ matrix.adapter }}://with_advisory:with_advisory_pass@0:${{ job.services.mysql.ports[3306] }}/with_advisory_lock_test
|
61
|
+
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
|
62
|
+
run: bundle exec rake
|
@@ -0,0 +1,64 @@
|
|
1
|
+
name: CI Postgresql
|
2
|
+
on:
|
3
|
+
pull_request:
|
4
|
+
branches:
|
5
|
+
- master
|
6
|
+
concurrency:
|
7
|
+
group: ci-postgresql-${{ github.head_ref }}
|
8
|
+
cancel-in-progress: true
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
minitest:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
name: CI Postgresql Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / Adapter ${{ matrix.adapter }}
|
14
|
+
services:
|
15
|
+
postgres:
|
16
|
+
image: 'postgres:16-alpine'
|
17
|
+
ports:
|
18
|
+
- '5432'
|
19
|
+
env:
|
20
|
+
POSTGRES_USER: with_advisory
|
21
|
+
POSTGRES_PASSWORD: with_advisory_pass
|
22
|
+
POSTGRES_DB: with_advisory_lock_test
|
23
|
+
options: >-
|
24
|
+
--health-cmd pg_isready
|
25
|
+
--health-interval 10s
|
26
|
+
--health-timeout 5s
|
27
|
+
--health-retries 5
|
28
|
+
strategy:
|
29
|
+
fail-fast: false
|
30
|
+
matrix:
|
31
|
+
ruby:
|
32
|
+
# - '3.2'
|
33
|
+
# - '3.1'
|
34
|
+
# - '3.0'
|
35
|
+
# - '2.7'
|
36
|
+
- '3.3'
|
37
|
+
- 'truffleruby'
|
38
|
+
rails:
|
39
|
+
- 7.1
|
40
|
+
- "7.0"
|
41
|
+
- 6.1
|
42
|
+
adapter:
|
43
|
+
- postgres
|
44
|
+
include:
|
45
|
+
- ruby: jruby
|
46
|
+
rails: 6.1
|
47
|
+
adapter: jdbcpostgresql
|
48
|
+
steps:
|
49
|
+
- name: Checkout
|
50
|
+
uses: actions/checkout@v4
|
51
|
+
- name: Setup Ruby
|
52
|
+
uses: ruby/setup-ruby@v1
|
53
|
+
with:
|
54
|
+
ruby-version: ${{ matrix.ruby }}
|
55
|
+
bundler-cache: true
|
56
|
+
rubygems: latest
|
57
|
+
env:
|
58
|
+
BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.rails }}.gemfile
|
59
|
+
- name: Test
|
60
|
+
env:
|
61
|
+
BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.rails }}.gemfile
|
62
|
+
DATABASE_URL: ${{ matrix.adapter }}://with_advisory:with_advisory_pass@localhost:${{ job.services.postgres.ports[5432] }}/with_advisory_lock_test
|
63
|
+
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
|
64
|
+
run: bundle exec rake
|
@@ -0,0 +1,54 @@
|
|
1
|
+
name: CI Sqlite3
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
|
8
|
+
concurrency:
|
9
|
+
group: ci-sqlite3-${{ github.head_ref }}
|
10
|
+
cancel-in-progress: true
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
minitest:
|
14
|
+
runs-on: ubuntu-latest
|
15
|
+
name: CI Sqlite3 Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / Adapter ${{ matrix.adapter }}
|
16
|
+
strategy:
|
17
|
+
fail-fast: false
|
18
|
+
matrix:
|
19
|
+
ruby:
|
20
|
+
# - '3.2'
|
21
|
+
# - '3.1'
|
22
|
+
# - '3.0'
|
23
|
+
# - '2.7'
|
24
|
+
- '3.3'
|
25
|
+
- 'truffleruby'
|
26
|
+
rails:
|
27
|
+
- 7.1
|
28
|
+
- "7.0"
|
29
|
+
- 6.1
|
30
|
+
adapter:
|
31
|
+
- sqlite3
|
32
|
+
include:
|
33
|
+
- ruby: jruby
|
34
|
+
rails: 6.1
|
35
|
+
adapter: jdbcsqlite3
|
36
|
+
steps:
|
37
|
+
- name: Checkout
|
38
|
+
uses: actions/checkout@v4
|
39
|
+
|
40
|
+
- name: Setup Ruby
|
41
|
+
uses: ruby/setup-ruby@v1
|
42
|
+
with:
|
43
|
+
ruby-version: ${{ matrix.ruby }}
|
44
|
+
bundler-cache: true
|
45
|
+
rubygems: latest
|
46
|
+
env:
|
47
|
+
BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.rails }}.gemfile
|
48
|
+
|
49
|
+
- name: Test
|
50
|
+
env:
|
51
|
+
BUNDLE_GEMFILE: gemfiles/activerecord_${{ matrix.rails }}.gemfile
|
52
|
+
DATABASE_URL: ${{ matrix.adapter }}:///tmp/test.sqlite3
|
53
|
+
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
|
54
|
+
run: bundle exec rake
|
@@ -1 +1 @@
|
|
1
|
-
{".":"5.
|
1
|
+
{".":"5.3.0"}
|
data/.ruby-version
ADDED
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 3.
|
1
|
+
ruby 3.4.4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
## Changelog
|
2
2
|
|
3
|
+
## [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
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* add #current_advisory_locks method ([#111](https://github.com/ClosureTree/with_advisory_lock/issues/111)) ([ccbd3b2](https://github.com/ClosureTree/with_advisory_lock/commit/ccbd3b23465f7fa1fc3800334159986c31d5c351))
|
9
|
+
|
10
|
+
## [5.2.0](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v5.1.0...with_advisory_lock/v5.2.0) (2025-04-24)
|
11
|
+
|
12
|
+
|
13
|
+
### Features
|
14
|
+
|
15
|
+
* 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))
|
16
|
+
|
17
|
+
|
18
|
+
### Bug Fixes
|
19
|
+
|
20
|
+
* Removed MySQL unused lock variable and broaden SQLite detection. ([#94](https://github.com/ClosureTree/with_advisory_lock/issues/94)) ([f818a18](https://github.com/ClosureTree/with_advisory_lock/commit/f818a181dde6711c8439c4cbf67c4525a09d346e))
|
21
|
+
|
3
22
|
## [5.1.0](https://github.com/ClosureTree/with_advisory_lock/compare/with_advisory_lock/v5.0.1...with_advisory_lock/v5.1.0) (2024-01-21)
|
4
23
|
|
5
24
|
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
SPDX-License-Identifier: MIT
|
2
|
+
SPDX-FileCopyrightText: 2013 Matthew McEachen
|
3
|
+
SPDX-FileCopyrightText: 2013-2025 Abdelkader Boudih
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining
|
6
6
|
a copy of this software and associated documentation files (the
|
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
19
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
20
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
21
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Makefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
.PHONY: test-pg test-mysql
|
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
|
7
|
+
|
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
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
version: "3.9"
|
2
|
+
services:
|
3
|
+
pg:
|
4
|
+
image: postgres:16
|
5
|
+
environment:
|
6
|
+
POSTGRES_USER: with_advisory
|
7
|
+
POSTGRES_PASSWORD: with_advisory_pass
|
8
|
+
POSTGRES_DB: with_advisory_lock_test
|
9
|
+
ports:
|
10
|
+
- "5432:5432"
|
11
|
+
mysql:
|
12
|
+
image: mysql:8
|
13
|
+
environment:
|
14
|
+
MYSQL_USER: with_advisory
|
15
|
+
MYSQL_PASSWORD: with_advisory_pass
|
16
|
+
MYSQL_DATABASE: with_advisory_lock_test
|
17
|
+
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
|
18
|
+
MYSQL_ROOT_HOST: '%'
|
19
|
+
ports:
|
20
|
+
- "3306:3306"
|
@@ -36,7 +36,7 @@ module WithAdvisoryLock
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def lock_str
|
39
|
-
@lock_str ||= "#{ENV[
|
39
|
+
@lock_str ||= "#{ENV[LOCK_PREFIX_ENV]}#{lock_name}"
|
40
40
|
end
|
41
41
|
|
42
42
|
def lock_stack_item
|
@@ -56,7 +56,7 @@ module WithAdvisoryLock
|
|
56
56
|
def with_advisory_lock_if_needed(&block)
|
57
57
|
if disable_query_cache
|
58
58
|
return lock_and_yield do
|
59
|
-
|
59
|
+
connection.uncached(&block)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -2,25 +2,22 @@
|
|
2
2
|
|
3
3
|
module WithAdvisoryLock
|
4
4
|
class DatabaseAdapterSupport
|
5
|
-
|
6
|
-
@@mysql_nl_cache = {}
|
7
|
-
@@mysql_nl_cache_mutex = Mutex.new
|
8
|
-
|
5
|
+
attr_reader :adapter_name
|
9
6
|
def initialize(connection)
|
10
7
|
@connection = connection
|
11
|
-
@
|
8
|
+
@adapter_name = connection.adapter_name.downcase.to_sym
|
12
9
|
end
|
13
10
|
|
14
11
|
def mysql?
|
15
|
-
%i[mysql2 trilogy].include?
|
12
|
+
%i[mysql2 trilogy].include? adapter_name
|
16
13
|
end
|
17
14
|
|
18
15
|
def postgresql?
|
19
|
-
%i[postgresql empostgresql postgis].include?
|
16
|
+
%i[postgresql empostgresql postgis].include? adapter_name
|
20
17
|
end
|
21
18
|
|
22
19
|
def sqlite?
|
23
|
-
|
20
|
+
[:sqlite3, :sqlite].include? adapter_name
|
24
21
|
end
|
25
22
|
end
|
26
23
|
end
|
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
module WithAdvisoryLock
|
4
4
|
class MySQL < Base
|
5
|
-
# See https://dev.mysql.com/doc/refman/5.7/en/
|
5
|
+
# See https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html
|
6
|
+
# See https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html
|
6
7
|
def try_lock
|
7
8
|
raise ArgumentError, 'shared locks are not supported on MySQL' if shared
|
8
9
|
raise ArgumentError, 'transaction level locks are not supported on MySQL' if transaction
|
@@ -15,8 +16,12 @@ module WithAdvisoryLock
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def execute_successful?(mysql_function)
|
18
|
-
|
19
|
-
|
19
|
+
execute_query(mysql_function) == 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute_query(mysql_function)
|
23
|
+
sql = "SELECT #{mysql_function}"
|
24
|
+
connection.query_value(sql)
|
20
25
|
end
|
21
26
|
|
22
27
|
# MySQL wants a string as the lock key.
|
@@ -2,42 +2,65 @@
|
|
2
2
|
|
3
3
|
module WithAdvisoryLock
|
4
4
|
class PostgreSQL < Base
|
5
|
-
# See
|
5
|
+
# See https://www.postgresql.org/docs/16/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
|
6
|
+
|
7
|
+
# MRI returns 't', jruby returns true. YAY!
|
8
|
+
LOCK_RESULT_VALUES = ['t', true].freeze
|
9
|
+
PG_ADVISORY_UNLOCK = 'pg_advisory_unlock'
|
10
|
+
PG_TRY_ADVISORY = 'pg_try_advisory'
|
11
|
+
ERROR_MESSAGE_REGEX = / ERROR: +current transaction is aborted,/
|
12
|
+
|
6
13
|
def try_lock
|
7
|
-
|
8
|
-
execute_successful?(pg_function)
|
14
|
+
execute_successful?(advisory_try_lock_function(transaction))
|
9
15
|
end
|
10
16
|
|
11
17
|
def release_lock
|
12
18
|
return if transaction
|
13
19
|
|
14
|
-
|
15
|
-
execute_successful?(pg_function)
|
20
|
+
execute_successful?(advisory_unlock_function)
|
16
21
|
rescue ActiveRecord::StatementInvalid => e
|
17
|
-
raise unless e.message =~
|
22
|
+
raise unless e.message =~ ERROR_MESSAGE_REGEX
|
18
23
|
|
19
24
|
begin
|
20
25
|
connection.rollback_db_transaction
|
21
|
-
execute_successful?(
|
26
|
+
execute_successful?(advisory_unlock_function)
|
22
27
|
ensure
|
23
28
|
connection.begin_db_transaction
|
24
29
|
end
|
25
30
|
end
|
26
31
|
|
32
|
+
def advisory_try_lock_function(transaction_scope)
|
33
|
+
[
|
34
|
+
'pg_try_advisory',
|
35
|
+
transaction_scope ? '_xact' : nil,
|
36
|
+
'_lock',
|
37
|
+
shared ? '_shared' : nil
|
38
|
+
].compact.join
|
39
|
+
end
|
40
|
+
|
41
|
+
def advisory_unlock_function
|
42
|
+
[
|
43
|
+
'pg_advisory_unlock',
|
44
|
+
shared ? '_shared' : nil
|
45
|
+
].compact.join
|
46
|
+
end
|
47
|
+
|
27
48
|
def execute_successful?(pg_function)
|
49
|
+
result = connection.select_value(prepare_sql(pg_function))
|
50
|
+
LOCK_RESULT_VALUES.include?(result)
|
51
|
+
end
|
52
|
+
|
53
|
+
def prepare_sql(pg_function)
|
28
54
|
comment = lock_name.to_s.gsub(%r{(/\*)|(\*/)}, '--')
|
29
|
-
|
30
|
-
result = connection.select_value(sql)
|
31
|
-
# MRI returns 't', jruby returns true. YAY!
|
32
|
-
['t', true].include?(result)
|
55
|
+
"SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name} /* #{comment} */"
|
33
56
|
end
|
34
57
|
|
35
58
|
# PostgreSQL wants 2 32bit integers as the lock key.
|
36
59
|
def lock_keys
|
37
|
-
@lock_keys ||= [
|
38
|
-
|
39
|
-
|
40
|
-
|
60
|
+
@lock_keys ||= [
|
61
|
+
stable_hashcode(lock_name),
|
62
|
+
ENV[LOCK_PREFIX_ENV]
|
63
|
+
].map { |ea| ea.to_i & 0x7fffffff }
|
41
64
|
end
|
42
65
|
end
|
43
66
|
end
|
data/lib/with_advisory_lock.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -17,7 +17,8 @@ end
|
|
17
17
|
|
18
18
|
ActiveRecord::Base.configurations = {
|
19
19
|
default_env: {
|
20
|
-
url: ENV.fetch('DATABASE_URL', "sqlite3://#{Dir.tmpdir}
|
20
|
+
url: ENV.fetch('DATABASE_URL', "sqlite3://#{Dir.tmpdir}/with_advisory_lock_test#{RUBY_VERSION}-#{ActiveRecord.gem_version}.sqlite3"),
|
21
|
+
pool: 20,
|
21
22
|
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
|
22
23
|
}
|
23
24
|
}
|
@@ -38,15 +39,28 @@ require 'maxitest/autorun'
|
|
38
39
|
require 'mocha/minitest'
|
39
40
|
|
40
41
|
class GemTestCase < ActiveSupport::TestCase
|
42
|
+
|
43
|
+
parallelize(workers: 1)
|
44
|
+
def adapter_support
|
45
|
+
@adapter_support ||= WithAdvisoryLock::DatabaseAdapterSupport.new(ActiveRecord::Base.connection)
|
46
|
+
end
|
47
|
+
def is_sqlite3_adapter?; adapter_support.sqlite?; end
|
48
|
+
def is_mysql_adapter?; adapter_support.mysql?; end
|
49
|
+
def is_postgresql_adapter?; adapter_support.postgresql?; end
|
50
|
+
|
41
51
|
setup do
|
42
|
-
ENV['FLOCK_DIR'] = Dir.mktmpdir
|
43
|
-
|
44
|
-
|
45
|
-
|
52
|
+
ENV['FLOCK_DIR'] = Dir.mktmpdir if is_sqlite3_adapter?
|
53
|
+
ApplicationRecord.connection.truncate_tables(
|
54
|
+
Tag.table_name,
|
55
|
+
TagAudit.table_name,
|
56
|
+
Label.table_name
|
57
|
+
)
|
46
58
|
end
|
59
|
+
|
47
60
|
teardown do
|
48
|
-
FileUtils.remove_entry_secure
|
61
|
+
FileUtils.remove_entry_secure(ENV['FLOCK_DIR'], true) if is_sqlite3_adapter?
|
49
62
|
end
|
50
63
|
end
|
51
64
|
|
52
65
|
puts "Testing with #{env_db} database, ActiveRecord #{ActiveRecord.gem_version} and #{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} as #{RUBY_VERSION}"
|
66
|
+
puts "Connection Pool size: #{ActiveRecord::Base.connection_pool.size}"
|
data/test/test_models.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
ActiveRecord::Schema.define(version:
|
3
|
+
ActiveRecord::Schema.define(version: 1) do
|
4
4
|
create_table 'tags', force: true do |t|
|
5
5
|
t.string 'name'
|
6
6
|
end
|
@@ -12,15 +12,19 @@ ActiveRecord::Schema.define(version: 0) do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
class
|
15
|
+
class ApplicationRecord < ActiveRecord::Base
|
16
|
+
self.abstract_class = true
|
17
|
+
end
|
18
|
+
|
19
|
+
class Tag < ApplicationRecord
|
16
20
|
after_save do
|
17
21
|
TagAudit.create(tag_name: name)
|
18
22
|
Label.create(name: name)
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
|
-
class TagAudit <
|
26
|
+
class TagAudit < ApplicationRecord
|
23
27
|
end
|
24
28
|
|
25
|
-
class Label <
|
29
|
+
class Label < ApplicationRecord
|
26
30
|
end
|
@@ -22,12 +22,12 @@ end
|
|
22
22
|
|
23
23
|
class ActiveRecordQueryCacheTest < GemTestCase
|
24
24
|
test 'does not disable quary cache by default' do
|
25
|
-
|
25
|
+
Tag.connection.expects(:uncached).never
|
26
26
|
Tag.with_advisory_lock('lock') { Tag.first }
|
27
27
|
end
|
28
28
|
|
29
29
|
test 'can disable ActiveRecord query cache' do
|
30
|
-
|
30
|
+
Tag.connection.expects(:uncached).once
|
31
31
|
Tag.with_advisory_lock('a-lock', disable_query_cache: true) { Tag.first }
|
32
32
|
end
|
33
33
|
end
|
@@ -77,4 +77,35 @@ class LockTest < GemTestCase
|
|
77
77
|
|
78
78
|
assert_equal(expected, actual)
|
79
79
|
end
|
80
|
+
|
81
|
+
test 'current_advisory_locks returns empty array outside an advisory lock request' do
|
82
|
+
assert_equal([], Tag.current_advisory_locks)
|
83
|
+
end
|
84
|
+
|
85
|
+
test 'current_advisory_locks returns an array with names of the acquired locks' do
|
86
|
+
Tag.with_advisory_lock(@lock_name) do
|
87
|
+
locks = Tag.current_advisory_locks
|
88
|
+
assert_equal(1, locks.size)
|
89
|
+
assert_match(/#{@lock_name}/, locks.first)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
test 'current_advisory_locks returns array of all nested lock names' do
|
94
|
+
first_lock = 'outer lock'
|
95
|
+
second_lock = 'inner lock'
|
96
|
+
|
97
|
+
Tag.with_advisory_lock(first_lock) do
|
98
|
+
Tag.with_advisory_lock(second_lock) do
|
99
|
+
locks = Tag.current_advisory_locks
|
100
|
+
assert_equal(2, locks.size)
|
101
|
+
assert_match(/#{first_lock}/, locks.first)
|
102
|
+
assert_match(/#{second_lock}/, locks.last)
|
103
|
+
end
|
104
|
+
|
105
|
+
locks = Tag.current_advisory_locks
|
106
|
+
assert_equal(1, locks.size)
|
107
|
+
assert_match(/#{first_lock}/, locks.first)
|
108
|
+
end
|
109
|
+
assert_equal([], Tag.current_advisory_locks)
|
110
|
+
end
|
80
111
|
end
|
@@ -15,7 +15,7 @@ class FindOrCreateWorker
|
|
15
15
|
|
16
16
|
def work_later
|
17
17
|
sleep
|
18
|
-
|
18
|
+
ApplicationRecord.connection_pool.with_connection do
|
19
19
|
if @use_advisory_lock
|
20
20
|
Tag.with_advisory_lock(@name) { work }
|
21
21
|
else
|
@@ -46,11 +46,11 @@ class ParallelismTest < GemTestCase
|
|
46
46
|
workers.each(&:join)
|
47
47
|
end
|
48
48
|
# Ensure we're still connected:
|
49
|
-
|
49
|
+
ApplicationRecord.connection_pool.connection
|
50
50
|
end
|
51
51
|
|
52
52
|
setup do
|
53
|
-
|
53
|
+
ApplicationRecord.connection.reconnect!
|
54
54
|
@workers = 10
|
55
55
|
end
|
56
56
|
|
@@ -24,7 +24,7 @@ class SharedTestWorker
|
|
24
24
|
private
|
25
25
|
|
26
26
|
def work
|
27
|
-
|
27
|
+
Tag.connection_pool.with_connection do
|
28
28
|
Tag.with_advisory_lock('test', timeout_seconds: 0, shared: @shared) do
|
29
29
|
@locked = true
|
30
30
|
sleep 0.01 until @cleanup
|
@@ -117,7 +117,7 @@ class SupportedEnvironmentTest < SharedLocksTest
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def pg_lock_modes
|
120
|
-
|
120
|
+
Tag.connection.select_values("SELECT mode FROM pg_locks WHERE locktype = 'advisory';")
|
121
121
|
end
|
122
122
|
|
123
123
|
test 'allows shared lock to be upgraded to an exclusive lock' do
|
@@ -10,7 +10,7 @@ class SeparateThreadTest < GemTestCase
|
|
10
10
|
@t1_return_value = nil
|
11
11
|
|
12
12
|
@t1 = Thread.new do
|
13
|
-
|
13
|
+
Label.connection_pool.with_connection do
|
14
14
|
@t1_return_value = Label.with_advisory_lock(@lock_name) do
|
15
15
|
@mutex.synchronize { @t1_acquired_lock = true }
|
16
16
|
sleep
|
@@ -21,7 +21,7 @@ class SeparateThreadTest < GemTestCase
|
|
21
21
|
|
22
22
|
# Wait for the thread to acquire the lock:
|
23
23
|
sleep(0.1) until @mutex.synchronize { @t1_acquired_lock }
|
24
|
-
|
24
|
+
Label.connection.reconnect!
|
25
25
|
end
|
26
26
|
|
27
27
|
teardown do
|
@@ -25,7 +25,7 @@ class TransactionScopingTest < GemTestCase
|
|
25
25
|
setup do
|
26
26
|
skip unless env_db == :postgresql
|
27
27
|
@pg_lock_count = lambda do
|
28
|
-
|
28
|
+
ApplicationRecord.connection.select_value("SELECT COUNT(*) FROM pg_locks WHERE locktype = 'advisory';").to_i
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
data/with_advisory_lock.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
17
|
spec.test_files = spec.files.grep(%r{^test/})
|
18
18
|
spec.require_paths = %w[lib]
|
19
|
-
spec.metadata = { '
|
19
|
+
spec.metadata = { 'rubygems_mfa_required' => 'true' }
|
20
20
|
spec.required_ruby_version = '>= 2.7.0'
|
21
21
|
spec.metadata['yard.run'] = 'yri'
|
22
22
|
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: with_advisory_lock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
8
8
|
- Abdelkader Boudih
|
9
|
-
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: activerecord
|
@@ -117,17 +116,23 @@ executables: []
|
|
117
116
|
extensions: []
|
118
117
|
extra_rdoc_files: []
|
119
118
|
files:
|
120
|
-
- ".github/workflows/ci.yml"
|
119
|
+
- ".github/workflows/ci-mysql5.yml"
|
120
|
+
- ".github/workflows/ci-mysql8.yml"
|
121
|
+
- ".github/workflows/ci-postgresql.yml"
|
122
|
+
- ".github/workflows/ci-sqlite3.yml"
|
121
123
|
- ".github/workflows/release.yml"
|
122
124
|
- ".gitignore"
|
123
125
|
- ".release-please-manifest.json"
|
126
|
+
- ".ruby-version"
|
124
127
|
- ".tool-versions"
|
125
128
|
- Appraisals
|
126
129
|
- CHANGELOG.md
|
127
130
|
- Gemfile
|
128
131
|
- LICENSE.txt
|
132
|
+
- Makefile
|
129
133
|
- README.md
|
130
134
|
- Rakefile
|
135
|
+
- docker-compose.yml
|
131
136
|
- gemfiles/activerecord_6.1.gemfile
|
132
137
|
- gemfiles/activerecord_7.0.gemfile
|
133
138
|
- gemfiles/activerecord_7.1.gemfile
|
@@ -141,27 +146,27 @@ files:
|
|
141
146
|
- lib/with_advisory_lock/postgresql.rb
|
142
147
|
- lib/with_advisory_lock/version.rb
|
143
148
|
- release-please-config.json
|
144
|
-
- test/concern_test.rb
|
145
|
-
- test/lock_test.rb
|
146
|
-
- test/nesting_test.rb
|
147
|
-
- test/options_test.rb
|
148
|
-
- test/parallelism_test.rb
|
149
|
-
- test/shared_test.rb
|
150
149
|
- test/test_helper.rb
|
151
150
|
- test/test_models.rb
|
152
|
-
- test/
|
153
|
-
- test/
|
151
|
+
- test/with_advisory_lock/base_test.rb
|
152
|
+
- test/with_advisory_lock/concern_test.rb
|
153
|
+
- test/with_advisory_lock/lock_test.rb
|
154
|
+
- test/with_advisory_lock/nesting_test.rb
|
155
|
+
- test/with_advisory_lock/options_test.rb
|
156
|
+
- test/with_advisory_lock/parallelism_test.rb
|
157
|
+
- test/with_advisory_lock/shared_test.rb
|
158
|
+
- test/with_advisory_lock/thread_test.rb
|
159
|
+
- test/with_advisory_lock/transaction_test.rb
|
154
160
|
- with_advisory_lock.gemspec
|
155
161
|
homepage: https://github.com/ClosureTree/with_advisory_lock
|
156
162
|
licenses:
|
157
163
|
- MIT
|
158
164
|
metadata:
|
159
|
-
|
165
|
+
rubygems_mfa_required: 'true'
|
160
166
|
yard.run: yri
|
161
167
|
homepage_uri: https://github.com/ClosureTree/with_advisory_lock
|
162
168
|
source_code_uri: https://github.com/ClosureTree/with_advisory_lock
|
163
169
|
changelog_uri: https://github.com/ClosureTree/with_advisory_lock/blob/master/CHANGELOG.md
|
164
|
-
post_install_message:
|
165
170
|
rdoc_options: []
|
166
171
|
require_paths:
|
167
172
|
- lib
|
@@ -176,18 +181,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
181
|
- !ruby/object:Gem::Version
|
177
182
|
version: '0'
|
178
183
|
requirements: []
|
179
|
-
rubygems_version: 3.
|
180
|
-
signing_key:
|
184
|
+
rubygems_version: 3.6.7
|
181
185
|
specification_version: 4
|
182
186
|
summary: Advisory locking for ActiveRecord
|
183
187
|
test_files:
|
184
|
-
- test/concern_test.rb
|
185
|
-
- test/lock_test.rb
|
186
|
-
- test/nesting_test.rb
|
187
|
-
- test/options_test.rb
|
188
|
-
- test/parallelism_test.rb
|
189
|
-
- test/shared_test.rb
|
190
188
|
- test/test_helper.rb
|
191
189
|
- test/test_models.rb
|
192
|
-
- test/
|
193
|
-
- test/
|
190
|
+
- test/with_advisory_lock/base_test.rb
|
191
|
+
- test/with_advisory_lock/concern_test.rb
|
192
|
+
- test/with_advisory_lock/lock_test.rb
|
193
|
+
- test/with_advisory_lock/nesting_test.rb
|
194
|
+
- test/with_advisory_lock/options_test.rb
|
195
|
+
- test/with_advisory_lock/parallelism_test.rb
|
196
|
+
- test/with_advisory_lock/shared_test.rb
|
197
|
+
- test/with_advisory_lock/thread_test.rb
|
198
|
+
- test/with_advisory_lock/transaction_test.rb
|
data/.github/workflows/ci.yml
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
---
|
2
|
-
name: CI
|
3
|
-
|
4
|
-
on:
|
5
|
-
pull_request:
|
6
|
-
branches:
|
7
|
-
- master
|
8
|
-
|
9
|
-
jobs:
|
10
|
-
minitest:
|
11
|
-
runs-on: ubuntu-latest
|
12
|
-
services:
|
13
|
-
mysql:
|
14
|
-
image: mysql/mysql-server:5.7
|
15
|
-
ports:
|
16
|
-
- "3306:3306"
|
17
|
-
env:
|
18
|
-
MYSQL_ROOT_PASSWORD: root
|
19
|
-
MYSQL_DATABASE: with_advisory_lock_test
|
20
|
-
MYSQL_ROOT_HOST: '%'
|
21
|
-
postgres:
|
22
|
-
image: 'postgres:14-alpine'
|
23
|
-
ports: ['5432:5432']
|
24
|
-
env:
|
25
|
-
POSTGRES_USER: closure_tree
|
26
|
-
POSTGRES_PASSWORD: closure_tree
|
27
|
-
POSTGRES_DB: with_advisory_lock_test
|
28
|
-
options: >-
|
29
|
-
--health-cmd pg_isready
|
30
|
-
--health-interval 10s
|
31
|
-
--health-timeout 5s
|
32
|
-
--health-retries 5
|
33
|
-
|
34
|
-
strategy:
|
35
|
-
fail-fast: false
|
36
|
-
matrix:
|
37
|
-
ruby:
|
38
|
-
- '3.2'
|
39
|
-
- '3.1'
|
40
|
-
- '3.0'
|
41
|
-
- '2.7'
|
42
|
-
- 'truffleruby'
|
43
|
-
rails:
|
44
|
-
- activerecord_7.1
|
45
|
-
- activerecord_7.0
|
46
|
-
- activerecord_6.1
|
47
|
-
adapter:
|
48
|
-
- sqlite3:///tmp/test.sqlite3
|
49
|
-
- mysql2://root:root@0/with_advisory_lock_test
|
50
|
-
- trilogy://root:root@0/with_advisory_lock_test
|
51
|
-
- postgres://closure_tree:closure_tree@0/with_advisory_lock_test
|
52
|
-
include:
|
53
|
-
- ruby: jruby
|
54
|
-
rails: activerecord_6.1
|
55
|
-
adapter: jdbcmysql://root:root@0/with_advisory_lock_test
|
56
|
-
- ruby: jruby
|
57
|
-
rails: activerecord_6.1
|
58
|
-
adapter: jdbcsqlite3:///tmp/test.sqlite3
|
59
|
-
- ruby: jruby
|
60
|
-
rails: activerecord_6.1
|
61
|
-
adapter: jdbcpostgresql://closure_tree:closure_tree@0/with_advisory_lock_test
|
62
|
-
steps:
|
63
|
-
- name: Checkout
|
64
|
-
uses: actions/checkout@v4
|
65
|
-
|
66
|
-
- name: Setup Ruby
|
67
|
-
uses: ruby/setup-ruby@v1
|
68
|
-
with:
|
69
|
-
ruby-version: ${{ matrix.ruby }}
|
70
|
-
bundler-cache: true
|
71
|
-
rubygems: latest
|
72
|
-
env:
|
73
|
-
BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile
|
74
|
-
|
75
|
-
- name: Test
|
76
|
-
env:
|
77
|
-
BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile
|
78
|
-
DATABASE_URL: ${{ matrix.adapter }}
|
79
|
-
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
|
80
|
-
run: bundle exec rake
|
File without changes
|
File without changes
|