pg_advisory_lock 0.2.0 → 0.4.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/tests.yml +69 -0
- data/.rubocop.yml +1 -1
- data/README.md +5 -3
- data/lib/pg_advisory_lock/base.rb +69 -25
- data/lib/pg_advisory_lock/lock_not_obtained.rb +6 -0
- data/lib/pg_advisory_lock/version.rb +1 -1
- data/lib/pg_advisory_lock.rb +1 -0
- data/pg_advisory_lock.gemspec +1 -1
- metadata +7 -6
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a63a1943d6c02f12078939e1c72cad482972a9493ec408a0984308b329e269b
|
|
4
|
+
data.tar.gz: fcf0e101ff148fa73651759f0dbe4fe32cbaac1be1b5e9a13f26a7816c2d3a15
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12b30ae854f94924bbced7bd831f4bbe89e682a50a0d01c299b7c3071c79e04e1d8cadadc5017574138d328104dc118f35f0fcee4e69889ea3ebb01bb0b8c8a8
|
|
7
|
+
data.tar.gz: 33ce47ca69b19de4985d8bf9d7517fecb5d9074bf3fe5ce53518b2b880ae6a7e1110f607a2160a6e8b8ee3928938603c75d53e957ee3f8e7dca1f5961b38682b
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
rubocop:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
name: Rubocop lint
|
|
12
|
+
env:
|
|
13
|
+
RAILS_VERSION: '~> 6.0'
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v2
|
|
16
|
+
- uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: 2.7
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
- name: Run rubocop
|
|
21
|
+
run: |
|
|
22
|
+
gem install bundler
|
|
23
|
+
bundle install
|
|
24
|
+
bundle exec rake rubocop
|
|
25
|
+
test:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
strategy:
|
|
28
|
+
matrix:
|
|
29
|
+
ruby: [ '2.7', '3.0', '3.1' ]
|
|
30
|
+
rails: [ '~> 6.0', '~> 7.0' ]
|
|
31
|
+
name: Tests with Ruby ${{ matrix.ruby }} Activerecord ${{ matrix.rails }}
|
|
32
|
+
services:
|
|
33
|
+
# Label used to access the service container
|
|
34
|
+
postgres:
|
|
35
|
+
# Docker Hub image
|
|
36
|
+
image: postgres
|
|
37
|
+
# Provide the password for postgres
|
|
38
|
+
env:
|
|
39
|
+
POSTGRES_DB: postgres_db
|
|
40
|
+
POSTGRES_PORT: 5432
|
|
41
|
+
POSTGRES_USER: postgres_user
|
|
42
|
+
POSTGRES_PASSWORD: postgres_password
|
|
43
|
+
ports:
|
|
44
|
+
- 5432:5432
|
|
45
|
+
# Set health checks to wait until postgres has started
|
|
46
|
+
options: >-
|
|
47
|
+
--health-cmd pg_isready
|
|
48
|
+
--health-interval 10s
|
|
49
|
+
--health-timeout 5s
|
|
50
|
+
--health-retries 5
|
|
51
|
+
env:
|
|
52
|
+
RAILS_VERSION: ${{ matrix.rails }}
|
|
53
|
+
POSTGRES_HOST: localhost
|
|
54
|
+
POSTGRES_DB: postgres_db
|
|
55
|
+
POSTGRES_PORT: 5432
|
|
56
|
+
POSTGRES_USER: postgres_user
|
|
57
|
+
POSTGRES_PASSWORD: postgres_password
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v2
|
|
60
|
+
- uses: ruby/setup-ruby@v1
|
|
61
|
+
with:
|
|
62
|
+
ruby-version: ${{ matrix.ruby }}
|
|
63
|
+
bundler-cache: true
|
|
64
|
+
- name: Run tests
|
|
65
|
+
run: |
|
|
66
|
+
gem install bundler
|
|
67
|
+
bundle install
|
|
68
|
+
cp -v spec/config/database.ci.yml spec/config/database.yml
|
|
69
|
+
bundle exec rake spec
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# PgAdvisoryLock
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
[](https://badge.fury.io/rb/pg_advisory_lock)
|
|
5
|
+
|
|
6
|
+
|
|
3
7
|
Postgresql Advisory Lock for ActiveRecord.
|
|
4
8
|
Allows to use mutex in applications that uses same database.
|
|
5
9
|
|
|
@@ -24,8 +28,6 @@ Or install it yourself as:
|
|
|
24
28
|
create subclass from `PgAdvisoryLock::Base` and define `model_class` for it
|
|
25
29
|
|
|
26
30
|
```ruby
|
|
27
|
-
require 'pg_sql_caller'
|
|
28
|
-
|
|
29
31
|
class MySqlCaller < PgAdvisoryLock::Base
|
|
30
32
|
model_class 'ApplicationRecord'
|
|
31
33
|
end
|
|
@@ -44,7 +46,7 @@ PgAdvisoryLock::Base.select_values 'SELECT id from users WHERE parent_name = ?',
|
|
|
44
46
|
## Development
|
|
45
47
|
|
|
46
48
|
After checking out the repo, run `bin/setup` to install dependencies.
|
|
47
|
-
Create `spec/config/database.yml` (look at `spec/config/database.
|
|
49
|
+
Create `spec/config/database.yml` (look at `spec/config/database.example.yml` for example).
|
|
48
50
|
You need to create test database, so run `psql -c 'CREATE DATABASE pg_advisory_lock_test;'`.
|
|
49
51
|
Then, run `rake spec` to run the tests.
|
|
50
52
|
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
@@ -23,6 +23,8 @@ module PgAdvisoryLock
|
|
|
23
23
|
# @param name [Symbol, String]
|
|
24
24
|
# @param value [Integer, Array<(Integer, Integer)>]
|
|
25
25
|
def register_lock(name, value)
|
|
26
|
+
raise ArgumentError, 'value must be integer or array of integers' unless int_or_array_of_ints?(value)
|
|
27
|
+
|
|
26
28
|
_lock_names[name.to_sym] = value
|
|
27
29
|
end
|
|
28
30
|
|
|
@@ -31,6 +33,8 @@ module PgAdvisoryLock
|
|
|
31
33
|
self._sql_caller_class = klass
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
# Locks specific advisory lock.
|
|
37
|
+
# If it's already locked just waits.
|
|
34
38
|
# @param name [Symbol, String] - lock name (will be transformed to number).
|
|
35
39
|
# @param transaction [Boolean] - if true lock will be released at the end of current transaction
|
|
36
40
|
# otherwise it will be released at the end of block.
|
|
@@ -41,7 +45,33 @@ module PgAdvisoryLock
|
|
|
41
45
|
# @raise [ArgumentError] when lock name is invalid.
|
|
42
46
|
# @return yield
|
|
43
47
|
def with_lock(name, transaction: true, shared: false, id: nil, &block)
|
|
44
|
-
new(name, transaction: transaction, shared: shared, id: id).lock(&block)
|
|
48
|
+
new(name, transaction: transaction, shared: shared, id: id, wait: true).lock(&block)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Tries to lock specific advisory lock.
|
|
52
|
+
# If it's already locked raises exception.
|
|
53
|
+
# @param name [Symbol, String] - lock name (will be transformed to number).
|
|
54
|
+
# @param transaction [Boolean] - if true lock will be released at the end of current transaction
|
|
55
|
+
# otherwise it will be released at the end of block.
|
|
56
|
+
# @param shared [Boolean] - is lock shared or not.
|
|
57
|
+
# @param id [Integer] - number that will be used in pair with lock number to perform advisory lock.
|
|
58
|
+
# @yield - call block when lock is acquired
|
|
59
|
+
# block must be passed if transaction argument is false.
|
|
60
|
+
# @raise [ArgumentError] when lock name is invalid.
|
|
61
|
+
# @raise [PgAdvisoryLock::LockNotObtained] when wait: false passed and lock already locked.
|
|
62
|
+
# @return yield
|
|
63
|
+
def try_lock(name, transaction: true, shared: false, id: nil, &block)
|
|
64
|
+
new(name, transaction: transaction, shared: shared, id: id, wait: false).lock(&block)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def int_or_array_of_ints?(value)
|
|
70
|
+
return true if value.is_a?(Integer)
|
|
71
|
+
|
|
72
|
+
return true if value.is_a?(Array) && value.all? { |i| i.is_a?(Integer) }
|
|
73
|
+
|
|
74
|
+
false
|
|
45
75
|
end
|
|
46
76
|
end
|
|
47
77
|
|
|
@@ -50,27 +80,30 @@ module PgAdvisoryLock
|
|
|
50
80
|
# otherwise it will be released at the end of block.
|
|
51
81
|
# @param shared [Boolean] - is lock shared or not.
|
|
52
82
|
# @param id [Integer] - number that will be used in pair with lock number to perform advisory lock.
|
|
53
|
-
|
|
83
|
+
# @param wait [Boolean] - when locked by someone else: true - wait for lock, raise PgAdvisoryLock::LockNotObtained.
|
|
84
|
+
def initialize(name, transaction:, shared:, id:, wait:)
|
|
54
85
|
@name = name.to_sym
|
|
55
86
|
@transaction = transaction
|
|
56
87
|
@shared = shared
|
|
57
88
|
@id = id
|
|
89
|
+
@wait = wait
|
|
58
90
|
end
|
|
59
91
|
|
|
60
92
|
# @yield - call block when lock is acquired
|
|
61
93
|
# block must be passed if transaction argument is false.
|
|
62
94
|
# @raise [ArgumentError] when lock name is invalid.
|
|
95
|
+
# @raise [PgAdvisoryLock::LockNotObtained] when wait: false passed and lock already locked.
|
|
63
96
|
# @return yield
|
|
64
97
|
def lock(&block)
|
|
65
98
|
with_logger do
|
|
66
|
-
|
|
67
|
-
advisory_lock(
|
|
99
|
+
lock_args = build_lock_args
|
|
100
|
+
advisory_lock(lock_args, &block)
|
|
68
101
|
end
|
|
69
102
|
end
|
|
70
103
|
|
|
71
104
|
private
|
|
72
105
|
|
|
73
|
-
attr_reader :transaction, :shared, :name, :id
|
|
106
|
+
attr_reader :transaction, :shared, :name, :id, :wait
|
|
74
107
|
|
|
75
108
|
def with_logger
|
|
76
109
|
return yield if logger.nil? || !logger.respond_to?(:tagged)
|
|
@@ -78,57 +111,68 @@ module PgAdvisoryLock
|
|
|
78
111
|
logger.tagged(self.class.to_s, name.inspect) { yield }
|
|
79
112
|
end
|
|
80
113
|
|
|
81
|
-
def advisory_lock(
|
|
114
|
+
def advisory_lock(lock_args, &block)
|
|
82
115
|
if transaction
|
|
83
|
-
transaction_lock(
|
|
116
|
+
transaction_lock(lock_args, &block)
|
|
84
117
|
else
|
|
85
|
-
non_transaction_lock(
|
|
118
|
+
non_transaction_lock(lock_args, &block)
|
|
86
119
|
end
|
|
87
120
|
end
|
|
88
121
|
|
|
89
|
-
def transaction_lock(
|
|
122
|
+
def transaction_lock(lock_args)
|
|
90
123
|
raise ArgumentError, 'block required when not within transaction' if !block_given? && !sql_caller_class.transaction_open?
|
|
91
124
|
|
|
92
|
-
return perform_lock(
|
|
125
|
+
return perform_lock(lock_args) unless block_given?
|
|
93
126
|
|
|
94
127
|
sql_caller_class.transaction do
|
|
95
|
-
perform_lock(
|
|
128
|
+
perform_lock(lock_args)
|
|
96
129
|
yield
|
|
97
130
|
end
|
|
98
131
|
end
|
|
99
132
|
|
|
100
|
-
def non_transaction_lock(
|
|
133
|
+
def non_transaction_lock(lock_args)
|
|
101
134
|
raise ArgumentError, 'block required on transaction: false' unless block_given?
|
|
102
135
|
|
|
103
136
|
begin
|
|
104
|
-
perform_lock(
|
|
137
|
+
perform_lock(lock_args)
|
|
105
138
|
yield
|
|
106
139
|
ensure
|
|
107
|
-
perform_unlock(
|
|
140
|
+
perform_unlock(lock_args)
|
|
108
141
|
end
|
|
109
142
|
end
|
|
110
143
|
|
|
111
|
-
def perform_lock(
|
|
112
|
-
function_name = "
|
|
144
|
+
def perform_lock(lock_args)
|
|
145
|
+
function_name = "pg#{'_try' unless wait}_advisory#{'_xact' if transaction}_lock#{'_shared' if shared}"
|
|
113
146
|
|
|
114
|
-
|
|
147
|
+
if wait
|
|
148
|
+
sql_caller_class.execute("SELECT #{function_name}(#{lock_args})")
|
|
149
|
+
else
|
|
150
|
+
result = sql_caller_class.select_value("SELECT #{function_name}(#{lock_args})")
|
|
151
|
+
raise LockNotObtained, "#{self.class} can't obtain lock (#{name}, #{id.inspect})" unless result
|
|
152
|
+
end
|
|
115
153
|
end
|
|
116
154
|
|
|
117
|
-
def perform_unlock(
|
|
118
|
-
sql_caller_class.select_value("SELECT pg_advisory_unlock#{'_shared' if shared}(#{
|
|
155
|
+
def perform_unlock(lock_args)
|
|
156
|
+
sql_caller_class.select_value("SELECT pg_advisory_unlock#{'_shared' if shared}(#{lock_args})")
|
|
119
157
|
end
|
|
120
158
|
|
|
121
159
|
# Converts lock name to number, because pg advisory lock functions accept only bigint numbers.
|
|
122
160
|
# @return [String] lock number or two numbers delimited by comma.
|
|
123
|
-
def
|
|
124
|
-
|
|
161
|
+
def build_lock_args
|
|
162
|
+
lock_args = _lock_names.fetch(name) do
|
|
125
163
|
raise ArgumentError, "lock name #{name.inspect} is invalid, see #{self.class}::NAMES"
|
|
126
164
|
end
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
165
|
+
lock_args = Array.wrap(lock_args)
|
|
166
|
+
if id.present?
|
|
167
|
+
if id.is_a?(Integer)
|
|
168
|
+
lock_args.push(id)
|
|
169
|
+
else
|
|
170
|
+
lock_args.push("hashtext(#{sql_caller_class.connection.quote(id.to_s)})")
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
raise ArgumentError, "can't use lock name #{name.inspect} with id" if lock_args.size > 2
|
|
130
174
|
|
|
131
|
-
|
|
175
|
+
lock_args.join(', ')
|
|
132
176
|
end
|
|
133
177
|
|
|
134
178
|
def sql_caller_class
|
data/lib/pg_advisory_lock.rb
CHANGED
data/pg_advisory_lock.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_advisory_lock
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Denis Talakevich
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-02-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -44,14 +44,14 @@ dependencies:
|
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
47
|
+
version: 0.2.2
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
54
|
+
version: 0.2.2
|
|
55
55
|
description: Postgresql Advisory Lock for ActiveRecord.
|
|
56
56
|
email:
|
|
57
57
|
- senid231@gmail.com
|
|
@@ -59,10 +59,10 @@ executables: []
|
|
|
59
59
|
extensions: []
|
|
60
60
|
extra_rdoc_files: []
|
|
61
61
|
files:
|
|
62
|
+
- ".github/workflows/tests.yml"
|
|
62
63
|
- ".gitignore"
|
|
63
64
|
- ".rspec"
|
|
64
65
|
- ".rubocop.yml"
|
|
65
|
-
- ".travis.yml"
|
|
66
66
|
- CODE_OF_CONDUCT.md
|
|
67
67
|
- Gemfile
|
|
68
68
|
- LICENSE.txt
|
|
@@ -72,6 +72,7 @@ files:
|
|
|
72
72
|
- bin/setup
|
|
73
73
|
- lib/pg_advisory_lock.rb
|
|
74
74
|
- lib/pg_advisory_lock/base.rb
|
|
75
|
+
- lib/pg_advisory_lock/lock_not_obtained.rb
|
|
75
76
|
- lib/pg_advisory_lock/version.rb
|
|
76
77
|
- pg_advisory_lock.gemspec
|
|
77
78
|
homepage: https://github.com/didww/pg_advisory_lock
|
|
@@ -96,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
96
97
|
- !ruby/object:Gem::Version
|
|
97
98
|
version: '0'
|
|
98
99
|
requirements: []
|
|
99
|
-
rubygems_version: 3.
|
|
100
|
+
rubygems_version: 3.1.6
|
|
100
101
|
signing_key:
|
|
101
102
|
specification_version: 4
|
|
102
103
|
summary: Postgresql Advisory Lock for ActiveRecord
|