with_advisory_lock 4.6.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/.github/workflows/release.yml +17 -0
- data/.gitignore +2 -0
- data/.release-please-manifest.json +1 -0
- data/.ruby-version +2 -0
- data/.tool-versions +1 -1
- data/CHANGELOG.md +89 -0
- data/Gemfile +22 -3
- data/LICENSE.txt +4 -4
- data/Makefile +10 -0
- data/README.md +22 -39
- 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 +19 -0
- data/lib/with_advisory_lock/concern.rb +37 -30
- data/lib/with_advisory_lock/core_advisory.rb +110 -0
- data/lib/with_advisory_lock/failed_to_acquire_lock.rb +9 -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 +3 -1
- data/lib/with_advisory_lock.rb +38 -11
- data/release-please-config.json +9 -0
- 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/dummy/db/schema.rb +15 -0
- 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 +33 -0
- data/test/with_advisory_lock/concern_test.rb +79 -0
- data/test/with_advisory_lock/lock_test.rb +197 -0
- 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 +101 -0
- data/test/with_advisory_lock/postgresql_race_condition_test.rb +118 -0
- data/test/with_advisory_lock/shared_test.rb +129 -0
- data/test/with_advisory_lock/thread_test.rb +83 -0
- data/test/with_advisory_lock/transaction_test.rb +83 -0
- data/with_advisory_lock.gemspec +54 -28
- metadata +83 -69
- data/.travis.yml +0 -38
- data/Appraisals +0 -29
- data/gemfiles/activerecord_4.2.gemfile +0 -19
- data/gemfiles/activerecord_5.0.gemfile +0 -19
- data/gemfiles/activerecord_5.1.gemfile +0 -19
- data/gemfiles/activerecord_5.2.gemfile +0 -19
- data/gemfiles/activerecord_6.0.gemfile +0 -19
- data/lib/with_advisory_lock/base.rb +0 -104
- data/lib/with_advisory_lock/database_adapter_support.rb +0 -63
- data/lib/with_advisory_lock/flock.rb +0 -32
- data/lib/with_advisory_lock/mysql.rb +0 -27
- data/lib/with_advisory_lock/mysql_no_nesting.rb +0 -20
- data/lib/with_advisory_lock/nested_advisory_lock_error.rb +0 -14
- data/lib/with_advisory_lock/postgresql.rb +0 -41
- data/test/concern_test.rb +0 -20
- data/test/database.yml +0 -17
- data/test/lock_test.rb +0 -47
- data/test/minitest_helper.rb +0 -40
- data/test/nesting_test.rb +0 -93
- data/test/options_test.rb +0 -64
- data/test/parallelism_test.rb +0 -77
- data/test/shared_test.rb +0 -131
- data/test/test_models.rb +0 -24
- data/test/thread_test.rb +0 -60
- data/test/transaction_test.rb +0 -70
- data/tests.sh +0 -11
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: with_advisory_lock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 7.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
8
|
-
|
8
|
+
- Abdelkader Boudih
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,44 +16,30 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '7.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '7.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: zeitwerk
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
type: :
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: minitest
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
33
|
+
version: '2.7'
|
34
|
+
type: :runtime
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
38
|
- - ">="
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
40
|
+
version: '2.7'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: maxitest
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - ">="
|
@@ -95,7 +81,7 @@ dependencies:
|
|
95
81
|
- !ruby/object:Gem::Version
|
96
82
|
version: '0'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
84
|
+
name: yard
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
100
86
|
requirements:
|
101
87
|
- - ">="
|
@@ -111,52 +97,92 @@ dependencies:
|
|
111
97
|
description: Advisory locking for ActiveRecord
|
112
98
|
email:
|
113
99
|
- matthew+github@mceachen.org
|
100
|
+
- terminale@gmail.com
|
114
101
|
executables: []
|
115
102
|
extensions: []
|
116
103
|
extra_rdoc_files: []
|
117
104
|
files:
|
105
|
+
- ".github/workflows/ci.yml"
|
106
|
+
- ".github/workflows/release.yml"
|
118
107
|
- ".gitignore"
|
108
|
+
- ".release-please-manifest.json"
|
109
|
+
- ".ruby-version"
|
119
110
|
- ".tool-versions"
|
120
|
-
- ".travis.yml"
|
121
|
-
- Appraisals
|
122
111
|
- CHANGELOG.md
|
123
112
|
- Gemfile
|
124
113
|
- LICENSE.txt
|
114
|
+
- Makefile
|
125
115
|
- README.md
|
126
116
|
- Rakefile
|
127
|
-
-
|
128
|
-
-
|
129
|
-
-
|
130
|
-
-
|
131
|
-
-
|
117
|
+
- bin/console
|
118
|
+
- bin/rails
|
119
|
+
- bin/sanity
|
120
|
+
- bin/sanity_check
|
121
|
+
- bin/setup
|
122
|
+
- bin/setup_test_db
|
123
|
+
- bin/test_connections
|
124
|
+
- docker-compose.yml
|
132
125
|
- lib/with_advisory_lock.rb
|
133
|
-
- lib/with_advisory_lock/base.rb
|
134
126
|
- lib/with_advisory_lock/concern.rb
|
135
|
-
- lib/with_advisory_lock/
|
136
|
-
- lib/with_advisory_lock/
|
137
|
-
- lib/with_advisory_lock/
|
138
|
-
- lib/with_advisory_lock/
|
139
|
-
- lib/with_advisory_lock/
|
140
|
-
- lib/with_advisory_lock/
|
127
|
+
- lib/with_advisory_lock/core_advisory.rb
|
128
|
+
- lib/with_advisory_lock/failed_to_acquire_lock.rb
|
129
|
+
- lib/with_advisory_lock/jruby_adapter.rb
|
130
|
+
- lib/with_advisory_lock/lock_stack_item.rb
|
131
|
+
- lib/with_advisory_lock/mysql_advisory.rb
|
132
|
+
- lib/with_advisory_lock/postgresql_advisory.rb
|
133
|
+
- lib/with_advisory_lock/result.rb
|
141
134
|
- lib/with_advisory_lock/version.rb
|
142
|
-
-
|
143
|
-
- test/
|
144
|
-
- test/
|
145
|
-
- test/
|
146
|
-
- test/
|
147
|
-
- test/
|
148
|
-
- test/
|
149
|
-
- test/
|
150
|
-
- test/
|
151
|
-
- test/
|
152
|
-
- test/
|
153
|
-
-
|
135
|
+
- release-please-config.json
|
136
|
+
- test/dummy/Rakefile
|
137
|
+
- test/dummy/app/controllers/application_controller.rb
|
138
|
+
- test/dummy/app/models/application_record.rb
|
139
|
+
- test/dummy/app/models/label.rb
|
140
|
+
- test/dummy/app/models/mysql_label.rb
|
141
|
+
- test/dummy/app/models/mysql_record.rb
|
142
|
+
- test/dummy/app/models/mysql_tag.rb
|
143
|
+
- test/dummy/app/models/mysql_tag_audit.rb
|
144
|
+
- test/dummy/app/models/tag.rb
|
145
|
+
- test/dummy/app/models/tag_audit.rb
|
146
|
+
- test/dummy/config.ru
|
147
|
+
- test/dummy/config/application.rb
|
148
|
+
- test/dummy/config/boot.rb
|
149
|
+
- test/dummy/config/database.yml
|
150
|
+
- test/dummy/config/environment.rb
|
151
|
+
- test/dummy/config/routes.rb
|
152
|
+
- test/dummy/db/schema.rb
|
153
|
+
- test/dummy/db/secondary_schema.rb
|
154
|
+
- test/dummy/lib/tasks/db.rake
|
155
|
+
- test/sanity_check_test.rb
|
156
|
+
- test/test_helper.rb
|
157
|
+
- test/with_advisory_lock/concern_test.rb
|
158
|
+
- test/with_advisory_lock/lock_test.rb
|
159
|
+
- test/with_advisory_lock/multi_adapter_test.rb
|
160
|
+
- test/with_advisory_lock/mysql_release_lock_test.rb
|
161
|
+
- test/with_advisory_lock/parallelism_test.rb
|
162
|
+
- test/with_advisory_lock/postgresql_race_condition_test.rb
|
163
|
+
- test/with_advisory_lock/shared_test.rb
|
164
|
+
- test/with_advisory_lock/thread_test.rb
|
165
|
+
- test/with_advisory_lock/transaction_test.rb
|
154
166
|
- with_advisory_lock.gemspec
|
155
|
-
homepage: https://github.com/
|
167
|
+
homepage: https://github.com/ClosureTree/with_advisory_lock
|
156
168
|
licenses:
|
157
169
|
- MIT
|
158
|
-
metadata:
|
159
|
-
|
170
|
+
metadata:
|
171
|
+
rubygems_mfa_required: 'true'
|
172
|
+
yard.run: yri
|
173
|
+
homepage_uri: https://github.com/ClosureTree/with_advisory_lock
|
174
|
+
source_code_uri: https://github.com/ClosureTree/with_advisory_lock
|
175
|
+
changelog_uri: https://github.com/ClosureTree/with_advisory_lock/blob/master/CHANGELOG.md
|
176
|
+
post_install_message: "⚠️ IMPORTANT: Total rewrite in Rust/COBOL! ⚠️\n\nNow that
|
177
|
+
I got your attention...\n\nThis version contains a complete internal rewrite. While
|
178
|
+
the public API \nremains the same, please test thoroughly before upgrading production
|
179
|
+
systems.\n\nNew features:\n- Mixed adapters are now fully supported! You can use
|
180
|
+
PostgreSQL and MySQL\n in the same application with different models.\n\nBreaking
|
181
|
+
changes:\n- SQLite support has been removed\n- MySQL 5.7 is no longer supported
|
182
|
+
(use MySQL 8+)\n- Rails 7.1 is no longer supported (use Rails 7.2+)\n- Private APIs
|
183
|
+
have been removed (Base, DatabaseAdapterSupport, etc.)\n\nIf your code relies on
|
184
|
+
private APIs or unsupported databases, lock to an \nolder version or update your
|
185
|
+
code accordingly.\n"
|
160
186
|
rdoc_options: []
|
161
187
|
require_paths:
|
162
188
|
- lib
|
@@ -164,26 +190,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
164
190
|
requirements:
|
165
191
|
- - ">="
|
166
192
|
- !ruby/object:Gem::Version
|
167
|
-
version:
|
193
|
+
version: 3.3.0
|
168
194
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
195
|
requirements:
|
170
196
|
- - ">="
|
171
197
|
- !ruby/object:Gem::Version
|
172
198
|
version: '0'
|
173
199
|
requirements: []
|
174
|
-
rubygems_version: 3.
|
175
|
-
signing_key:
|
200
|
+
rubygems_version: 3.6.9
|
176
201
|
specification_version: 4
|
177
202
|
summary: Advisory locking for ActiveRecord
|
178
|
-
test_files:
|
179
|
-
- test/concern_test.rb
|
180
|
-
- test/database.yml
|
181
|
-
- test/lock_test.rb
|
182
|
-
- test/minitest_helper.rb
|
183
|
-
- test/nesting_test.rb
|
184
|
-
- test/options_test.rb
|
185
|
-
- test/parallelism_test.rb
|
186
|
-
- test/shared_test.rb
|
187
|
-
- test/test_models.rb
|
188
|
-
- test/thread_test.rb
|
189
|
-
- test/transaction_test.rb
|
203
|
+
test_files: []
|
data/.travis.yml
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
|
3
|
-
services:
|
4
|
-
- postgresql
|
5
|
-
- mysql
|
6
|
-
|
7
|
-
addons:
|
8
|
-
postgresql: "10"
|
9
|
-
|
10
|
-
rvm:
|
11
|
-
- 2.6.4
|
12
|
-
- 2.5.6
|
13
|
-
- 2.4.7
|
14
|
-
|
15
|
-
gemfile:
|
16
|
-
- gemfiles/activerecord_6.0.gemfile
|
17
|
-
- gemfiles/activerecord_5.2.gemfile
|
18
|
-
- gemfiles/activerecord_5.1.gemfile
|
19
|
-
- gemfiles/activerecord_5.0.gemfile
|
20
|
-
- gemfiles/activerecord_4.2.gemfile
|
21
|
-
|
22
|
-
env:
|
23
|
-
global:
|
24
|
-
- WITH_ADVISORY_LOCK_PREFIX=$TRAVIS_JOB_ID
|
25
|
-
matrix:
|
26
|
-
- DB=postgresql
|
27
|
-
- DB=mysql MYSQL_VERSION=5.7
|
28
|
-
- DB=sqlite
|
29
|
-
matrix:
|
30
|
-
exclude:
|
31
|
-
- rvm: 2.4.7
|
32
|
-
gemfile: gemfiles/activerecord_6.0.gemfile
|
33
|
-
|
34
|
-
before_install:
|
35
|
-
- mysql -e 'create database with_advisory_lock_test'
|
36
|
-
- psql -c 'create database with_advisory_lock_test' -U postgres
|
37
|
-
|
38
|
-
script: bundle exec rake --trace
|
data/Appraisals
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
appraise "activerecord-4.2" do
|
2
|
-
gem "activerecord", "~> 4.2.0"
|
3
|
-
platforms :ruby do
|
4
|
-
gem "pg", "~> 0.21"
|
5
|
-
gem "mysql2", "< 0.5"
|
6
|
-
gem "sqlite3", "~> 1.3.6"
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
appraise "activerecord-5.0" do
|
11
|
-
gem "activerecord", "~> 5.0.0"
|
12
|
-
platforms :ruby do
|
13
|
-
gem "sqlite3", "~> 1.3.6"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
appraise "activerecord-5.1" do
|
18
|
-
gem "activerecord", "~> 5.1.0"
|
19
|
-
gem "sqlite3", "~> 1.3.6"
|
20
|
-
end
|
21
|
-
|
22
|
-
appraise "activerecord-5.2" do
|
23
|
-
gem "activerecord", "~> 5.1.0"
|
24
|
-
gem "sqlite3", "~> 1.3.6"
|
25
|
-
end
|
26
|
-
|
27
|
-
appraise "activerecord-6.0" do
|
28
|
-
gem "activerecord", "~> 6.0.0"
|
29
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "activerecord", "~> 4.2.0"
|
6
|
-
|
7
|
-
platforms :ruby do
|
8
|
-
gem "mysql2", "< 0.5"
|
9
|
-
gem "pg", "~> 0.21"
|
10
|
-
gem "sqlite3", "~> 1.3.6"
|
11
|
-
end
|
12
|
-
|
13
|
-
platforms :jruby do
|
14
|
-
gem "activerecord-jdbcmysql-adapter"
|
15
|
-
gem "activerecord-jdbcpostgresql-adapter"
|
16
|
-
gem "activerecord-jdbcsqlite3-adapter"
|
17
|
-
end
|
18
|
-
|
19
|
-
gemspec path: "../"
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "activerecord", "~> 5.0.0"
|
6
|
-
|
7
|
-
platforms :ruby do
|
8
|
-
gem "mysql2"
|
9
|
-
gem "pg"
|
10
|
-
gem "sqlite3", "~> 1.3.6"
|
11
|
-
end
|
12
|
-
|
13
|
-
platforms :jruby do
|
14
|
-
gem "activerecord-jdbcmysql-adapter"
|
15
|
-
gem "activerecord-jdbcpostgresql-adapter"
|
16
|
-
gem "activerecord-jdbcsqlite3-adapter"
|
17
|
-
end
|
18
|
-
|
19
|
-
gemspec path: "../"
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "activerecord", "~> 5.1.0"
|
6
|
-
|
7
|
-
platforms :ruby do
|
8
|
-
gem "mysql2"
|
9
|
-
gem "pg"
|
10
|
-
gem "sqlite3", "~> 1.3.6"
|
11
|
-
end
|
12
|
-
|
13
|
-
platforms :jruby do
|
14
|
-
gem "activerecord-jdbcmysql-adapter"
|
15
|
-
gem "activerecord-jdbcpostgresql-adapter"
|
16
|
-
gem "activerecord-jdbcsqlite3-adapter"
|
17
|
-
end
|
18
|
-
|
19
|
-
gemspec path: "../"
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "activerecord", "~> 5.1.0"
|
6
|
-
|
7
|
-
platforms :ruby do
|
8
|
-
gem "mysql2"
|
9
|
-
gem "pg"
|
10
|
-
gem "sqlite3", "~> 1.3.6"
|
11
|
-
end
|
12
|
-
|
13
|
-
platforms :jruby do
|
14
|
-
gem "activerecord-jdbcmysql-adapter"
|
15
|
-
gem "activerecord-jdbcpostgresql-adapter"
|
16
|
-
gem "activerecord-jdbcsqlite3-adapter"
|
17
|
-
end
|
18
|
-
|
19
|
-
gemspec path: "../"
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "activerecord", "~> 6.0.0"
|
6
|
-
|
7
|
-
platforms :ruby do
|
8
|
-
gem "mysql2"
|
9
|
-
gem "pg"
|
10
|
-
gem "sqlite3"
|
11
|
-
end
|
12
|
-
|
13
|
-
platforms :jruby do
|
14
|
-
gem "activerecord-jdbcmysql-adapter"
|
15
|
-
gem "activerecord-jdbcpostgresql-adapter"
|
16
|
-
gem "activerecord-jdbcsqlite3-adapter"
|
17
|
-
end
|
18
|
-
|
19
|
-
gemspec path: "../"
|
@@ -1,104 +0,0 @@
|
|
1
|
-
require 'zlib'
|
2
|
-
|
3
|
-
module WithAdvisoryLock
|
4
|
-
class Result
|
5
|
-
attr_reader :result
|
6
|
-
|
7
|
-
def initialize(lock_was_acquired, result = false)
|
8
|
-
@lock_was_acquired = lock_was_acquired
|
9
|
-
@result = result
|
10
|
-
end
|
11
|
-
|
12
|
-
def lock_was_acquired?
|
13
|
-
@lock_was_acquired
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
FAILED_TO_LOCK = Result.new(false)
|
18
|
-
|
19
|
-
LockStackItem = Struct.new(:name, :shared)
|
20
|
-
|
21
|
-
class Base
|
22
|
-
attr_reader :connection, :lock_name, :timeout_seconds, :shared, :transaction
|
23
|
-
|
24
|
-
def initialize(connection, lock_name, options)
|
25
|
-
options = { timeout_seconds: options } unless options.respond_to?(:fetch)
|
26
|
-
options.assert_valid_keys :timeout_seconds, :shared, :transaction
|
27
|
-
|
28
|
-
@connection = connection
|
29
|
-
@lock_name = lock_name
|
30
|
-
@timeout_seconds = options.fetch(:timeout_seconds, nil)
|
31
|
-
@shared = options.fetch(:shared, false)
|
32
|
-
@transaction = options.fetch(:transaction, false)
|
33
|
-
end
|
34
|
-
|
35
|
-
def lock_str
|
36
|
-
@lock_str ||= "#{ENV['WITH_ADVISORY_LOCK_PREFIX']}#{lock_name}"
|
37
|
-
end
|
38
|
-
|
39
|
-
def lock_stack_item
|
40
|
-
@lock_stack_item ||= LockStackItem.new(lock_str, shared)
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.lock_stack
|
44
|
-
# access doesn't need to be synchronized as it is only accessed by the current thread.
|
45
|
-
Thread.current[:with_advisory_lock_stack] ||= []
|
46
|
-
end
|
47
|
-
delegate :lock_stack, to: 'self.class'
|
48
|
-
|
49
|
-
def already_locked?
|
50
|
-
lock_stack.include? lock_stack_item
|
51
|
-
end
|
52
|
-
|
53
|
-
def with_advisory_lock_if_needed(&block)
|
54
|
-
if already_locked?
|
55
|
-
Result.new(true, yield)
|
56
|
-
elsif timeout_seconds == 0
|
57
|
-
yield_with_lock(&block)
|
58
|
-
else
|
59
|
-
yield_with_lock_and_timeout(&block)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def stable_hashcode(input)
|
64
|
-
if input.is_a? Numeric
|
65
|
-
input.to_i
|
66
|
-
else
|
67
|
-
# Ruby MRI's String#hash is randomly seeded as of Ruby 1.9 so
|
68
|
-
# make sure we use a deterministic hash.
|
69
|
-
Zlib.crc32(input.to_s)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def yield_with_lock_and_timeout(&block)
|
74
|
-
give_up_at = Time.now + @timeout_seconds if @timeout_seconds
|
75
|
-
while @timeout_seconds.nil? || Time.now < give_up_at
|
76
|
-
r = yield_with_lock(&block)
|
77
|
-
return r if r.lock_was_acquired?
|
78
|
-
# Randomizing sleep time may help reduce contention.
|
79
|
-
sleep(rand(0.05..0.15))
|
80
|
-
end
|
81
|
-
FAILED_TO_LOCK
|
82
|
-
end
|
83
|
-
|
84
|
-
def yield_with_lock
|
85
|
-
if try_lock
|
86
|
-
begin
|
87
|
-
lock_stack.push(lock_stack_item)
|
88
|
-
result = block_given? ? yield : nil
|
89
|
-
Result.new(true, result)
|
90
|
-
ensure
|
91
|
-
lock_stack.pop
|
92
|
-
release_lock
|
93
|
-
end
|
94
|
-
else
|
95
|
-
FAILED_TO_LOCK
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Prevent AR from caching results improperly
|
100
|
-
def unique_column_name
|
101
|
-
"t#{SecureRandom.hex}"
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
module WithAdvisoryLock
|
2
|
-
class DatabaseAdapterSupport
|
3
|
-
# Caches nested lock support by MySQL reported version
|
4
|
-
@@mysql_nl_cache = {}
|
5
|
-
@@mysql_nl_cache_mutex = Mutex.new
|
6
|
-
|
7
|
-
def initialize(connection)
|
8
|
-
@connection = connection
|
9
|
-
@sym_name = connection.adapter_name.downcase.to_sym
|
10
|
-
end
|
11
|
-
|
12
|
-
def mysql?
|
13
|
-
%i[mysql mysql2].include? @sym_name
|
14
|
-
end
|
15
|
-
|
16
|
-
# Nested lock support for MySQL was introduced in 5.7.5
|
17
|
-
# Checking by version number is complicated by MySQL compatible DBs (like MariaDB) having their own versioning schemes
|
18
|
-
# Therefore, we check for nested lock support by simply trying a nested lock, then testing and caching the outcome
|
19
|
-
def mysql_nested_lock_support?
|
20
|
-
return false unless mysql?
|
21
|
-
|
22
|
-
# We select the MySQL version this way and cache on it, as MySQL will report versions like "5.7.5", and MariaDB will
|
23
|
-
# report versions like "10.3.8-MariaDB", which allow us to cache on features without introducing problems.
|
24
|
-
version = @connection.select_value("SELECT version()")
|
25
|
-
|
26
|
-
@@mysql_nl_cache_mutex.synchronize do
|
27
|
-
return @@mysql_nl_cache[version] if @@mysql_nl_cache.keys.include?(version)
|
28
|
-
|
29
|
-
lock_1 = "\"nested-test-1-#{SecureRandom.hex}\""
|
30
|
-
lock_2 = "\"nested-test-2-#{SecureRandom.hex}\""
|
31
|
-
|
32
|
-
get_1 = @connection.select_value("SELECT GET_LOCK(#{lock_1}, 0) AS t#{SecureRandom.hex}")
|
33
|
-
get_2 = @connection.select_value("SELECT GET_LOCK(#{lock_2}, 0) AS t#{SecureRandom.hex}")
|
34
|
-
|
35
|
-
# Both locks should succeed in old and new MySQL versions with "1"
|
36
|
-
raise RuntimeError, "Unexpected nested lock acquire result #{get_1}, #{get_2}" unless [get_1, get_2] == [1, 1]
|
37
|
-
|
38
|
-
release_1 = @connection.select_value("SELECT RELEASE_LOCK(#{lock_1}) AS t#{SecureRandom.hex}")
|
39
|
-
release_2 = @connection.select_value("SELECT RELEASE_LOCK(#{lock_2}) AS t#{SecureRandom.hex}")
|
40
|
-
|
41
|
-
# In MySQL < 5.7.5 release_1 will return nil (not currently locked) and release_2 will return 1 (successfully unlocked)
|
42
|
-
# In MySQL >= 5.7.5 release_1 and release_2 will return 1 (both successfully unlocked)
|
43
|
-
# See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock for more
|
44
|
-
@@mysql_nl_cache[version] = case [release_1, release_2]
|
45
|
-
when [1, 1]
|
46
|
-
true
|
47
|
-
when [nil, 1]
|
48
|
-
false
|
49
|
-
else
|
50
|
-
raise RuntimeError, "Unexpected nested lock release result #{release_1}, #{release_2}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def postgresql?
|
56
|
-
%i[postgresql empostgresql postgis].include? @sym_name
|
57
|
-
end
|
58
|
-
|
59
|
-
def sqlite?
|
60
|
-
:sqlite3 == @sym_name
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
|
3
|
-
module WithAdvisoryLock
|
4
|
-
class Flock < Base
|
5
|
-
def filename
|
6
|
-
@filename ||= begin
|
7
|
-
safe = lock_str.to_s.gsub(/[^a-z0-9]/i, '')
|
8
|
-
fn = ".lock-#{safe}-#{stable_hashcode(lock_str)}"
|
9
|
-
# Let the user specify a directory besides CWD.
|
10
|
-
ENV['FLOCK_DIR'] ? File.expand_path(fn, ENV['FLOCK_DIR']) : fn
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def file_io
|
15
|
-
@file_io ||= begin
|
16
|
-
FileUtils.touch(filename)
|
17
|
-
File.open(filename, 'r+')
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def try_lock
|
22
|
-
if transaction
|
23
|
-
raise ArgumentError, 'transaction level locks are not supported on SQLite'
|
24
|
-
end
|
25
|
-
0 == file_io.flock((shared ? File::LOCK_SH : File::LOCK_EX) | File::LOCK_NB)
|
26
|
-
end
|
27
|
-
|
28
|
-
def release_lock
|
29
|
-
0 == file_io.flock(File::LOCK_UN)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module WithAdvisoryLock
|
2
|
-
# MySQL > 5.7.5 supports nested locks
|
3
|
-
class MySQL < Base
|
4
|
-
# See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
|
5
|
-
def try_lock
|
6
|
-
raise ArgumentError, 'shared locks are not supported on MySQL' if shared
|
7
|
-
if transaction
|
8
|
-
raise ArgumentError, 'transaction level locks are not supported on MySQL'
|
9
|
-
end
|
10
|
-
execute_successful?("GET_LOCK(#{quoted_lock_str}, 0)")
|
11
|
-
end
|
12
|
-
|
13
|
-
def release_lock
|
14
|
-
execute_successful?("RELEASE_LOCK(#{quoted_lock_str})")
|
15
|
-
end
|
16
|
-
|
17
|
-
def execute_successful?(mysql_function)
|
18
|
-
sql = "SELECT #{mysql_function} AS #{unique_column_name}"
|
19
|
-
connection.select_value(sql).to_i > 0
|
20
|
-
end
|
21
|
-
|
22
|
-
# MySQL wants a string as the lock key.
|
23
|
-
def quoted_lock_str
|
24
|
-
connection.quote(lock_str)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module WithAdvisoryLock
|
2
|
-
# For MySQL < 5.7.5 that does not support nested locks
|
3
|
-
class MySQLNoNesting < MySQL
|
4
|
-
# See http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock
|
5
|
-
def try_lock
|
6
|
-
unless lock_stack.empty?
|
7
|
-
raise NestedAdvisoryLockError.new(
|
8
|
-
"MySQL < 5.7.5 doesn't support nested Advisory Locks",
|
9
|
-
lock_stack.dup
|
10
|
-
)
|
11
|
-
end
|
12
|
-
super
|
13
|
-
end
|
14
|
-
|
15
|
-
# MySQL doesn't support nested locks:
|
16
|
-
def already_locked?
|
17
|
-
lock_stack.last == lock_stack_item
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module WithAdvisoryLock
|
2
|
-
class NestedAdvisoryLockError < StandardError
|
3
|
-
attr_accessor :lock_stack
|
4
|
-
|
5
|
-
def initialize(msg = nil, lock_stack = nil)
|
6
|
-
super(msg)
|
7
|
-
@lock_stack = lock_stack
|
8
|
-
end
|
9
|
-
|
10
|
-
def to_s
|
11
|
-
super + (lock_stack ? ": lock stack = #{lock_stack}" : '')
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|