with_advisory_lock 4.6.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +80 -0
  3. data/.github/workflows/release.yml +20 -0
  4. data/.gitignore +2 -0
  5. data/.release-please-manifest.json +1 -0
  6. data/.tool-versions +1 -1
  7. data/Appraisals +34 -18
  8. data/CHANGELOG.md +31 -0
  9. data/Gemfile +0 -12
  10. data/README.md +17 -6
  11. data/gemfiles/{activerecord_6.0.gemfile → activerecord_6.1.gemfile} +4 -2
  12. data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.0.gemfile} +4 -2
  13. data/gemfiles/activerecord_7.1.gemfile +14 -0
  14. data/lib/with_advisory_lock/base.rb +17 -3
  15. data/lib/with_advisory_lock/concern.rb +13 -17
  16. data/lib/with_advisory_lock/database_adapter_support.rb +4 -41
  17. data/lib/with_advisory_lock/failed_to_acquire_lock.rb +9 -0
  18. data/lib/with_advisory_lock/flock.rb +4 -3
  19. data/lib/with_advisory_lock/mysql.rb +5 -5
  20. data/lib/with_advisory_lock/postgresql.rb +9 -7
  21. data/lib/with_advisory_lock/version.rb +3 -1
  22. data/lib/with_advisory_lock.rb +8 -10
  23. data/release-please-config.json +9 -0
  24. data/test/concern_test.rb +23 -10
  25. data/test/lock_test.rb +61 -28
  26. data/test/nesting_test.rb +14 -79
  27. data/test/options_test.rb +35 -33
  28. data/test/parallelism_test.rb +35 -37
  29. data/test/shared_test.rb +93 -90
  30. data/test/test_helper.rb +52 -0
  31. data/test/test_models.rb +9 -7
  32. data/test/thread_test.rb +23 -22
  33. data/test/transaction_test.rb +34 -36
  34. data/with_advisory_lock.gemspec +29 -23
  35. metadata +32 -28
  36. data/.travis.yml +0 -38
  37. data/gemfiles/activerecord_4.2.gemfile +0 -19
  38. data/gemfiles/activerecord_5.0.gemfile +0 -19
  39. data/gemfiles/activerecord_5.1.gemfile +0 -19
  40. data/lib/with_advisory_lock/mysql_no_nesting.rb +0 -20
  41. data/lib/with_advisory_lock/nested_advisory_lock_error.rb +0 -14
  42. data/test/database.yml +0 -17
  43. data/test/minitest_helper.rb +0 -40
  44. data/tests.sh +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc4719c3f44e4cf1f219b582d21cf9757274ca1fcca6128c227798f32d4b589e
4
- data.tar.gz: c63b8be1ee91b25a519b5a4d7d090ff98172d6d3a476899b55c6cd03bf77eda6
3
+ metadata.gz: 6918df100f32e829701a38ad6b7dd30db20d7a83635b82cbd8ccc7f3022a88d5
4
+ data.tar.gz: f231c4fa8b5c403de000054839603e2297215929a64cccc63c21c577bc19ecd7
5
5
  SHA512:
6
- metadata.gz: be5a906b9740506447bfcdb1928cd5473add67e0a48acd32f8a26ca4671be49b34ca5eca987df2f8e6f29a0a3dbb0bccc3321061d9f1e5827a0ca4ef44341124
7
- data.tar.gz: 124358b251ffd9cc066d54aebfeeca97fb0db88ae18293fb7e0169ceb13960d298e72c66e9d5fbd79f0cbda5360d2fdb2e609866ac82eeb26b1eab6ead103daf
6
+ metadata.gz: 21a2201433600aa26b0f7b0176b3b4e87606a00459b70d9dfb10a385c00a53fa5f0365cd426c2b5793d1b0022fa37e663567b0daf185e4be93d5a59e10695e2e
7
+ data.tar.gz: b1e707a2115e83cd1667bb6b1842c7150b28da3603bd3ec668fc9918de01553fc10d44e65d89d94a3f7a183489da49b0348d1cc59650487d48482648041445df
@@ -0,0 +1,80 @@
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
@@ -0,0 +1,20 @@
1
+ name: release-please
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: write
11
+ pull-requests: write
12
+
13
+ jobs:
14
+ release-please:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: google-github-actions/release-please-action@v4
18
+ id: release
19
+ with:
20
+ command: manifest
data/.gitignore CHANGED
@@ -18,3 +18,5 @@ test/tmp
18
18
  test/version_tmp
19
19
  tmp
20
20
  *.iml
21
+ test/sqlite.db
22
+ .env
@@ -0,0 +1 @@
1
+ {".":"5.1.0"}
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 2.6.4
1
+ ruby 3.0.5
data/Appraisals CHANGED
@@ -1,29 +1,45 @@
1
- appraise "activerecord-4.2" do
2
- gem "activerecord", "~> 4.2.0"
1
+ # frozen_string_literal: true
2
+
3
+ appraise 'activerecord-7.1' do
4
+ gem 'activerecord', '~> 7.1.0'
3
5
  platforms :ruby do
4
- gem "pg", "~> 0.21"
5
- gem "mysql2", "< 0.5"
6
- gem "sqlite3", "~> 1.3.6"
6
+ gem 'sqlite3'
7
+ gem 'mysql2'
8
+ gem 'trilogy'
9
+ gem 'pg'
7
10
  end
8
11
  end
9
12
 
10
- appraise "activerecord-5.0" do
11
- gem "activerecord", "~> 5.0.0"
13
+ appraise 'activerecord-7.0' do
14
+ gem 'activerecord', '~> 7.0.0'
12
15
  platforms :ruby do
13
- gem "sqlite3", "~> 1.3.6"
16
+ gem 'sqlite3'
17
+ gem 'mysql2'
18
+ gem 'trilogy'
19
+ gem "activerecord-trilogy-adapter"
20
+ gem 'pg'
21
+ end
22
+ platforms :jruby do
23
+ gem "activerecord-jdbcmysql-adapter"
24
+ gem "activerecord-jdbcpostgresql-adapter"
25
+ gem "activerecord-jdbcsqlite3-adapter"
14
26
  end
15
27
  end
16
28
 
17
- appraise "activerecord-5.1" do
18
- gem "activerecord", "~> 5.1.0"
19
- gem "sqlite3", "~> 1.3.6"
20
- end
29
+ appraise 'activerecord-6.1' do
30
+ gem 'activerecord', '~> 6.1.0'
21
31
 
22
- appraise "activerecord-5.2" do
23
- gem "activerecord", "~> 5.1.0"
24
- gem "sqlite3", "~> 1.3.6"
32
+ platforms :ruby do
33
+ gem 'sqlite3'
34
+ gem 'mysql2'
35
+ gem 'trilogy'
36
+ gem "activerecord-trilogy-adapter"
37
+ gem 'pg'
38
+ end
39
+ platforms :jruby do
40
+ gem "activerecord-jdbcmysql-adapter"
41
+ gem "activerecord-jdbcpostgresql-adapter"
42
+ gem "activerecord-jdbcsqlite3-adapter"
43
+ end
25
44
  end
26
45
 
27
- appraise "activerecord-6.0" do
28
- gem "activerecord", "~> 6.0.0"
29
- end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  ## Changelog
2
2
 
3
+ ## [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
+
5
+
6
+ ### Features
7
+
8
+ * use zeitwerk loader instead of ActiveSupport::Autoload ([b5082fd](https://github.com/ClosureTree/with_advisory_lock/commit/b5082fddacacacff48139f5bf509601a37945a0e))
9
+
10
+ ## 5.0.1 (2024-01-21)
11
+
12
+
13
+ ### Features
14
+
15
+ * add release workflow ([5d32520](https://github.com/ClosureTree/with_advisory_lock/commit/5d325201c82974991381a9fbc4d1714c9739dc4f))
16
+ * add ruby 3.1 test/support ([#60](https://github.com/ClosureTree/with_advisory_lock/issues/60)) ([514f042](https://github.com/ClosureTree/with_advisory_lock/commit/514f0420d957ef30911a00d54685385bec5867c3))
17
+ * Add testing for activerecord 7.1 and support for trilogy adapter ([#77](https://github.com/ClosureTree/with_advisory_lock/issues/77)) ([69c23fe](https://github.com/ClosureTree/with_advisory_lock/commit/69c23fe09887fc5d97ac7b0194825c21efe244a5))
18
+ * add truffleruby support ([#62](https://github.com/ClosureTree/with_advisory_lock/issues/62)) ([ec34bd4](https://github.com/ClosureTree/with_advisory_lock/commit/ec34bd448e3505e5df631daaf47bb83f2f5316dc))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * User may sometimes pass in non-strings, such as integers ([#55](https://github.com/ClosureTree/with_advisory_lock/issues/55)) ([9885597](https://github.com/ClosureTree/with_advisory_lock/commit/988559747363ef00958fcf782317e76c40ffa2a3))
24
+
25
+ ### 5.0.0
26
+ - Drop support for EOL rubies and activerecord (ruby below 2.7 and activerecord below 6.1).
27
+ - Allow lock name to be integer
28
+ - Jruby support
29
+ - Truffleruby support
30
+ - Add `with_advisory_lock!`, which raises an error if the lock acquisition fails
31
+ - Add `disable_query_cache` option to `with_advisory_lock`
32
+ - Drop support for mysql < 5.7.5
33
+
3
34
  ### 4.6.0
4
35
 
5
36
  - Support for ActiveRecord 6
data/Gemfile CHANGED
@@ -1,15 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
-
5
- platforms :ruby do
6
- gem 'mysql2'
7
- gem 'pg'
8
- gem 'sqlite3'
9
- end
10
-
11
- platforms :jruby do
12
- gem 'activerecord-jdbcmysql-adapter'
13
- gem 'activerecord-jdbcpostgresql-adapter'
14
- gem 'activerecord-jdbcsqlite3-adapter'
15
- end
data/README.md CHANGED
@@ -1,14 +1,13 @@
1
1
  # with_advisory_lock
2
2
 
3
- Adds advisory locking (mutexes) to ActiveRecord 4.2, 5.x and 6.0, with ruby
4
- 2.4, 2.5 and 2.6, when used with
3
+ Adds advisory locking (mutexes) to ActiveRecord 6.0+, with ruby 2.7+, jruby or truffleruby, when used with
5
4
  [MySQL](https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_get-lock)
6
5
  or
7
6
  [PostgreSQL](https://www.postgresql.org/docs/current/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS).
8
7
  SQLite resorts to file locking.
9
8
 
10
- [![Build Status](https://api.travis-ci.org/ClosureTree/with_advisory_lock.svg?branch=master)](https://travis-ci.org/ClosureTree/with_advisory_lock)
11
9
  [![Gem Version](https://badge.fury.io/rb/with_advisory_lock.svg)](https://badge.fury.io/rb/with_advisory_lock)
10
+ [![CI](https://github.com/ClosureTree/with_advisory_lock/actions/workflows/ci.yml/badge.svg)](https://github.com/ClosureTree/with_advisory_lock/actions/workflows/ci.yml)
12
11
 
13
12
  ## What's an "Advisory Lock"?
14
13
 
@@ -45,8 +44,10 @@ A value of zero will try the lock only once. If the lock is acquired, the block
45
44
  will be yielded to. If the lock is currently being held, the block will not be
46
45
  called.
47
46
 
48
- Note that if a non-nil value is provided for `timeout_seconds`, the block will
49
- not be invoked if the lock cannot be acquired within that time-frame.
47
+ > **Note**
48
+ >
49
+ > If a non-nil value is provided for `timeout_seconds`, the block will
50
+ *not* be invoked if the lock cannot be acquired within that time-frame. In this case, `with_advisory_lock` will return `false`, while `with_advisory_lock!` will raise a `WithAdvisoryLock::FailedToAcquireLock` error.
50
51
 
51
52
  For backwards compatability, the timeout value can be specified directly as the
52
53
  second parameter.
@@ -80,6 +81,8 @@ block, if the lock was able to be acquired and the block yielded, or `false`, if
80
81
  you provided a timeout_seconds value and the lock was not able to be acquired in
81
82
  time.
82
83
 
84
+ `with_advisory_lock!` is similar to `with_advisory_lock`, but raises a `WithAdvisoryLock::FailedToAcquireLock` error if the lock was not able to be acquired in time.
85
+
83
86
  ### Testing for the current lock status
84
87
 
85
88
  If you needed to check if the advisory lock is currently being held, you can
@@ -91,6 +94,14 @@ If you want to see if the current Thread is holding a lock, you can call
91
94
  `Tag.current_advisory_lock` which will return the name of the current lock. If
92
95
  no lock is currently held, `.current_advisory_lock` returns `nil`.
93
96
 
97
+ ### ActiveRecord Query Cache
98
+
99
+ You can optionally pass `disable_query_cache: true` to the options hash of
100
+ `with_advisory_lock` in order to disable ActiveRecord's query cache. This can
101
+ prevent problems when you query the database from within the lock and it returns
102
+ stale results. More info on why this can be a problem can be
103
+ [found here](https://github.com/ClosureTree/with_advisory_lock/issues/52)
104
+
94
105
  ## Installation
95
106
 
96
107
  Add this line to your application's Gemfile:
@@ -123,7 +134,7 @@ row-level locks prevent concurrent modification to a given model.
123
134
 
124
135
  **If you're building a
125
136
  [CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete)
126
- application, this will be your most commonly used lock.**
137
+ application, this will be 2.4, 2.5 and your most commonly used lock.**
127
138
 
128
139
  ### Table-level locks
129
140
 
@@ -2,12 +2,14 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 6.0.0"
5
+ gem "activerecord", "~> 6.1.0"
6
6
 
7
7
  platforms :ruby do
8
+ gem "sqlite3"
8
9
  gem "mysql2"
10
+ gem "trilogy"
11
+ gem "activerecord-trilogy-adapter"
9
12
  gem "pg"
10
- gem "sqlite3"
11
13
  end
12
14
 
13
15
  platforms :jruby do
@@ -2,12 +2,14 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 5.1.0"
5
+ gem "activerecord", "~> 7.0.0"
6
6
 
7
7
  platforms :ruby do
8
+ gem "sqlite3"
8
9
  gem "mysql2"
10
+ gem "trilogy"
11
+ gem "activerecord-trilogy-adapter"
9
12
  gem "pg"
10
- gem "sqlite3", "~> 1.3.6"
11
13
  end
12
14
 
13
15
  platforms :jruby do
@@ -0,0 +1,14 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 7.1.0"
6
+
7
+ platforms :ruby do
8
+ gem "sqlite3"
9
+ gem "mysql2"
10
+ gem "trilogy"
11
+ gem "pg"
12
+ end
13
+
14
+ gemspec path: "../"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'zlib'
2
4
 
3
5
  module WithAdvisoryLock
@@ -19,17 +21,18 @@ module WithAdvisoryLock
19
21
  LockStackItem = Struct.new(:name, :shared)
20
22
 
21
23
  class Base
22
- attr_reader :connection, :lock_name, :timeout_seconds, :shared, :transaction
24
+ attr_reader :connection, :lock_name, :timeout_seconds, :shared, :transaction, :disable_query_cache
23
25
 
24
26
  def initialize(connection, lock_name, options)
25
27
  options = { timeout_seconds: options } unless options.respond_to?(:fetch)
26
- options.assert_valid_keys :timeout_seconds, :shared, :transaction
28
+ options.assert_valid_keys :timeout_seconds, :shared, :transaction, :disable_query_cache
27
29
 
28
30
  @connection = connection
29
31
  @lock_name = lock_name
30
32
  @timeout_seconds = options.fetch(:timeout_seconds, nil)
31
33
  @shared = options.fetch(:shared, false)
32
34
  @transaction = options.fetch(:transaction, false)
35
+ @disable_query_cache = options.fetch(:disable_query_cache, false)
33
36
  end
34
37
 
35
38
  def lock_str
@@ -51,6 +54,16 @@ module WithAdvisoryLock
51
54
  end
52
55
 
53
56
  def with_advisory_lock_if_needed(&block)
57
+ if disable_query_cache
58
+ return lock_and_yield do
59
+ ActiveRecord::Base.uncached(&block)
60
+ end
61
+ end
62
+
63
+ lock_and_yield(&block)
64
+ end
65
+
66
+ def lock_and_yield(&block)
54
67
  if already_locked?
55
68
  Result.new(true, yield)
56
69
  elsif timeout_seconds == 0
@@ -66,7 +79,7 @@ module WithAdvisoryLock
66
79
  else
67
80
  # Ruby MRI's String#hash is randomly seeded as of Ruby 1.9 so
68
81
  # make sure we use a deterministic hash.
69
- Zlib.crc32(input.to_s)
82
+ Zlib.crc32(input.to_s, 0)
70
83
  end
71
84
  end
72
85
 
@@ -75,6 +88,7 @@ module WithAdvisoryLock
75
88
  while @timeout_seconds.nil? || Time.now < give_up_at
76
89
  r = yield_with_lock(&block)
77
90
  return r if r.lock_was_acquired?
91
+
78
92
  # Randomizing sleep time may help reduce contention.
79
93
  sleep(rand(0.05..0.15))
80
94
  end
@@ -1,19 +1,25 @@
1
- require 'active_support/concern'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module WithAdvisoryLock
4
4
  module Concern
5
5
  extend ActiveSupport::Concern
6
- delegate :with_advisory_lock, :advisory_lock_exists?, to: 'self.class'
6
+ delegate :with_advisory_lock, :with_advisory_lock!, :advisory_lock_exists?, to: 'self.class'
7
7
 
8
- module ClassMethods
8
+ class_methods do
9
9
  def with_advisory_lock(lock_name, options = {}, &block)
10
10
  result = with_advisory_lock_result(lock_name, options, &block)
11
11
  result.lock_was_acquired? ? result.result : false
12
12
  end
13
13
 
14
+ def with_advisory_lock!(lock_name, options = {}, &block)
15
+ result = with_advisory_lock_result(lock_name, options, &block)
16
+ raise WithAdvisoryLock::FailedToAcquireLock, lock_name unless result.lock_was_acquired?
17
+
18
+ result.result
19
+ end
20
+
14
21
  def with_advisory_lock_result(lock_name, options = {}, &block)
15
- class_options = options.extract!(:force_nested_lock_support) if options.respond_to?(:fetch)
16
- impl = impl_class(class_options).new(connection, lock_name, options)
22
+ impl = impl_class.new(connection, lock_name, options)
17
23
  impl.with_advisory_lock_if_needed(&block)
18
24
  end
19
25
 
@@ -29,22 +35,12 @@ module WithAdvisoryLock
29
35
 
30
36
  private
31
37
 
32
- def impl_class(options = nil)
38
+ def impl_class
33
39
  adapter = WithAdvisoryLock::DatabaseAdapterSupport.new(connection)
34
40
  if adapter.postgresql?
35
41
  WithAdvisoryLock::PostgreSQL
36
42
  elsif adapter.mysql?
37
- nested_lock = if options.respond_to?(:fetch) && [true, false].include?(options.fetch(:force_nested_lock_support, nil))
38
- options.fetch(:force_nested_lock_support)
39
- else
40
- adapter.mysql_nested_lock_support?
41
- end
42
-
43
- if nested_lock
44
- WithAdvisoryLock::MySQL
45
- else
46
- WithAdvisoryLock::MySQLNoNesting
47
- end
43
+ WithAdvisoryLock::MySQL
48
44
  else
49
45
  WithAdvisoryLock::Flock
50
46
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WithAdvisoryLock
2
4
  class DatabaseAdapterSupport
3
5
  # Caches nested lock support by MySQL reported version
@@ -10,46 +12,7 @@ module WithAdvisoryLock
10
12
  end
11
13
 
12
14
  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
15
+ %i[mysql2 trilogy].include? @sym_name
53
16
  end
54
17
 
55
18
  def postgresql?
@@ -57,7 +20,7 @@ module WithAdvisoryLock
57
20
  end
58
21
 
59
22
  def sqlite?
60
- :sqlite3 == @sym_name
23
+ @sym_name == :sqlite3
61
24
  end
62
25
  end
63
26
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WithAdvisoryLock
4
+ class FailedToAcquireLock < StandardError
5
+ def initialize(lock_name)
6
+ super("Failed to acquire lock #{lock_name}")
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
 
3
5
  module WithAdvisoryLock
@@ -19,9 +21,8 @@ module WithAdvisoryLock
19
21
  end
20
22
 
21
23
  def try_lock
22
- if transaction
23
- raise ArgumentError, 'transaction level locks are not supported on SQLite'
24
- end
24
+ raise ArgumentError, 'transaction level locks are not supported on SQLite' if transaction
25
+
25
26
  0 == file_io.flock((shared ? File::LOCK_SH : File::LOCK_EX) | File::LOCK_NB)
26
27
  end
27
28
 
@@ -1,12 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WithAdvisoryLock
2
- # MySQL > 5.7.5 supports nested locks
3
4
  class MySQL < Base
4
5
  # See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
5
6
  def try_lock
6
7
  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
8
+ raise ArgumentError, 'transaction level locks are not supported on MySQL' if transaction
9
+
10
10
  execute_successful?("GET_LOCK(#{quoted_lock_str}, 0)")
11
11
  end
12
12
 
@@ -16,7 +16,7 @@ module WithAdvisoryLock
16
16
 
17
17
  def execute_successful?(mysql_function)
18
18
  sql = "SELECT #{mysql_function} AS #{unique_column_name}"
19
- connection.select_value(sql).to_i > 0
19
+ connection.select_value(sql).to_i.positive?
20
20
  end
21
21
 
22
22
  # MySQL wants a string as the lock key.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WithAdvisoryLock
2
4
  class PostgreSQL < Base
3
5
  # See http://www.postgresql.org/docs/9.1/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
@@ -8,10 +10,12 @@ module WithAdvisoryLock
8
10
 
9
11
  def release_lock
10
12
  return if transaction
13
+
11
14
  pg_function = "pg_advisory_unlock#{shared ? '_shared' : ''}"
12
15
  execute_successful?(pg_function)
13
16
  rescue ActiveRecord::StatementInvalid => e
14
17
  raise unless e.message =~ / ERROR: +current transaction is aborted,/
18
+
15
19
  begin
16
20
  connection.rollback_db_transaction
17
21
  execute_successful?(pg_function)
@@ -21,20 +25,18 @@ module WithAdvisoryLock
21
25
  end
22
26
 
23
27
  def execute_successful?(pg_function)
24
- comment = lock_name.gsub(/(\/\*)|(\*\/)/, '--')
28
+ comment = lock_name.to_s.gsub(%r{(/\*)|(\*/)}, '--')
25
29
  sql = "SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name} /* #{comment} */"
26
30
  result = connection.select_value(sql)
27
31
  # MRI returns 't', jruby returns true. YAY!
28
- (result == 't' || result == true)
32
+ ['t', true].include?(result)
29
33
  end
30
34
 
31
35
  # PostgreSQL wants 2 32bit integers as the lock key.
32
36
  def lock_keys
33
- @lock_keys ||= begin
34
- [stable_hashcode(lock_name), ENV['WITH_ADVISORY_LOCK_PREFIX']].map do |ea|
35
- # pg advisory args must be 31 bit ints
36
- ea.to_i & 0x7fffffff
37
- end
37
+ @lock_keys ||= [stable_hashcode(lock_name), ENV['WITH_ADVISORY_LOCK_PREFIX']].map do |ea|
38
+ # pg advisory args must be 31 bit ints
39
+ ea.to_i & 0x7fffffff
38
40
  end
39
41
  end
40
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WithAdvisoryLock
2
- VERSION = Gem::Version.new('4.6.0')
4
+ VERSION = Gem::Version.new('5.1.0')
3
5
  end