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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +76 -0
  3. data/.github/workflows/release.yml +17 -0
  4. data/.gitignore +2 -0
  5. data/.release-please-manifest.json +1 -0
  6. data/.ruby-version +2 -0
  7. data/.tool-versions +1 -1
  8. data/CHANGELOG.md +89 -0
  9. data/Gemfile +22 -3
  10. data/LICENSE.txt +4 -4
  11. data/Makefile +10 -0
  12. data/README.md +22 -39
  13. data/Rakefile +5 -2
  14. data/bin/console +11 -0
  15. data/bin/rails +15 -0
  16. data/bin/sanity +20 -0
  17. data/bin/sanity_check +86 -0
  18. data/bin/setup +8 -0
  19. data/bin/setup_test_db +59 -0
  20. data/bin/test_connections +22 -0
  21. data/docker-compose.yml +19 -0
  22. data/lib/with_advisory_lock/concern.rb +37 -30
  23. data/lib/with_advisory_lock/core_advisory.rb +110 -0
  24. data/lib/with_advisory_lock/failed_to_acquire_lock.rb +9 -0
  25. data/lib/with_advisory_lock/jruby_adapter.rb +29 -0
  26. data/lib/with_advisory_lock/lock_stack_item.rb +6 -0
  27. data/lib/with_advisory_lock/mysql_advisory.rb +71 -0
  28. data/lib/with_advisory_lock/postgresql_advisory.rb +112 -0
  29. data/lib/with_advisory_lock/result.rb +14 -0
  30. data/lib/with_advisory_lock/version.rb +3 -1
  31. data/lib/with_advisory_lock.rb +38 -11
  32. data/release-please-config.json +9 -0
  33. data/test/dummy/Rakefile +8 -0
  34. data/test/dummy/app/controllers/application_controller.rb +7 -0
  35. data/test/dummy/app/models/application_record.rb +6 -0
  36. data/test/dummy/app/models/label.rb +4 -0
  37. data/test/dummy/app/models/mysql_label.rb +5 -0
  38. data/test/dummy/app/models/mysql_record.rb +6 -0
  39. data/test/dummy/app/models/mysql_tag.rb +10 -0
  40. data/test/dummy/app/models/mysql_tag_audit.rb +5 -0
  41. data/test/dummy/app/models/tag.rb +8 -0
  42. data/test/dummy/app/models/tag_audit.rb +4 -0
  43. data/test/dummy/config/application.rb +31 -0
  44. data/test/dummy/config/boot.rb +3 -0
  45. data/test/dummy/config/database.yml +13 -0
  46. data/test/dummy/config/environment.rb +7 -0
  47. data/test/dummy/config/routes.rb +4 -0
  48. data/test/dummy/config.ru +6 -0
  49. data/test/dummy/db/schema.rb +15 -0
  50. data/test/dummy/db/secondary_schema.rb +15 -0
  51. data/test/dummy/lib/tasks/db.rake +40 -0
  52. data/test/sanity_check_test.rb +63 -0
  53. data/test/test_helper.rb +33 -0
  54. data/test/with_advisory_lock/concern_test.rb +79 -0
  55. data/test/with_advisory_lock/lock_test.rb +197 -0
  56. data/test/with_advisory_lock/multi_adapter_test.rb +17 -0
  57. data/test/with_advisory_lock/mysql_release_lock_test.rb +119 -0
  58. data/test/with_advisory_lock/parallelism_test.rb +101 -0
  59. data/test/with_advisory_lock/postgresql_race_condition_test.rb +118 -0
  60. data/test/with_advisory_lock/shared_test.rb +129 -0
  61. data/test/with_advisory_lock/thread_test.rb +83 -0
  62. data/test/with_advisory_lock/transaction_test.rb +83 -0
  63. data/with_advisory_lock.gemspec +54 -28
  64. metadata +83 -69
  65. data/.travis.yml +0 -38
  66. data/Appraisals +0 -29
  67. data/gemfiles/activerecord_4.2.gemfile +0 -19
  68. data/gemfiles/activerecord_5.0.gemfile +0 -19
  69. data/gemfiles/activerecord_5.1.gemfile +0 -19
  70. data/gemfiles/activerecord_5.2.gemfile +0 -19
  71. data/gemfiles/activerecord_6.0.gemfile +0 -19
  72. data/lib/with_advisory_lock/base.rb +0 -104
  73. data/lib/with_advisory_lock/database_adapter_support.rb +0 -63
  74. data/lib/with_advisory_lock/flock.rb +0 -32
  75. data/lib/with_advisory_lock/mysql.rb +0 -27
  76. data/lib/with_advisory_lock/mysql_no_nesting.rb +0 -20
  77. data/lib/with_advisory_lock/nested_advisory_lock_error.rb +0 -14
  78. data/lib/with_advisory_lock/postgresql.rb +0 -41
  79. data/test/concern_test.rb +0 -20
  80. data/test/database.yml +0 -17
  81. data/test/lock_test.rb +0 -47
  82. data/test/minitest_helper.rb +0 -40
  83. data/test/nesting_test.rb +0 -93
  84. data/test/options_test.rb +0 -64
  85. data/test/parallelism_test.rb +0 -77
  86. data/test/shared_test.rb +0 -131
  87. data/test/test_models.rb +0 -24
  88. data/test/thread_test.rb +0 -60
  89. data/test/transaction_test.rb +0 -70
  90. 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.6.0
4
+ version: 7.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
8
- autorequire:
8
+ - Abdelkader Boudih
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-20 00:00:00.000000000 Z
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: '4.2'
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: '4.2'
26
+ version: '7.2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: yard
28
+ name: zeitwerk
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
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: '0'
40
+ version: '2.7'
55
41
  - !ruby/object:Gem::Dependency
56
- name: minitest-great_expectations
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: appraisal
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
- - gemfiles/activerecord_4.2.gemfile
128
- - gemfiles/activerecord_5.0.gemfile
129
- - gemfiles/activerecord_5.1.gemfile
130
- - gemfiles/activerecord_5.2.gemfile
131
- - gemfiles/activerecord_6.0.gemfile
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/database_adapter_support.rb
136
- - lib/with_advisory_lock/flock.rb
137
- - lib/with_advisory_lock/mysql.rb
138
- - lib/with_advisory_lock/mysql_no_nesting.rb
139
- - lib/with_advisory_lock/nested_advisory_lock_error.rb
140
- - lib/with_advisory_lock/postgresql.rb
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
- - test/concern_test.rb
143
- - test/database.yml
144
- - test/lock_test.rb
145
- - test/minitest_helper.rb
146
- - test/nesting_test.rb
147
- - test/options_test.rb
148
- - test/parallelism_test.rb
149
- - test/shared_test.rb
150
- - test/test_models.rb
151
- - test/thread_test.rb
152
- - test/transaction_test.rb
153
- - tests.sh
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/mceachen/with_advisory_lock
167
+ homepage: https://github.com/ClosureTree/with_advisory_lock
156
168
  licenses:
157
169
  - MIT
158
- metadata: {}
159
- post_install_message:
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: 2.2.10
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.0.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