with_advisory_lock 4.6.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +80 -0
  3. data/.gitignore +2 -0
  4. data/.tool-versions +1 -1
  5. data/Appraisals +34 -18
  6. data/CHANGELOG.md +9 -0
  7. data/Gemfile +0 -12
  8. data/README.md +17 -6
  9. data/gemfiles/{activerecord_6.0.gemfile → activerecord_6.1.gemfile} +4 -2
  10. data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.0.gemfile} +4 -2
  11. data/gemfiles/activerecord_7.1.gemfile +14 -0
  12. data/lib/with_advisory_lock/base.rb +16 -2
  13. data/lib/with_advisory_lock/concern.rb +16 -16
  14. data/lib/with_advisory_lock/database_adapter_support.rb +4 -41
  15. data/lib/with_advisory_lock/failed_to_acquire_lock.rb +7 -0
  16. data/lib/with_advisory_lock/flock.rb +4 -3
  17. data/lib/with_advisory_lock/mysql.rb +5 -5
  18. data/lib/with_advisory_lock/postgresql.rb +9 -7
  19. data/lib/with_advisory_lock/version.rb +3 -1
  20. data/lib/with_advisory_lock.rb +1 -2
  21. data/test/concern_test.rb +23 -10
  22. data/test/lock_test.rb +61 -28
  23. data/test/nesting_test.rb +14 -79
  24. data/test/options_test.rb +35 -33
  25. data/test/parallelism_test.rb +35 -37
  26. data/test/shared_test.rb +93 -90
  27. data/test/test_helper.rb +52 -0
  28. data/test/test_models.rb +9 -7
  29. data/test/thread_test.rb +23 -22
  30. data/test/transaction_test.rb +34 -36
  31. data/with_advisory_lock.gemspec +24 -23
  32. metadata +25 -41
  33. data/.travis.yml +0 -38
  34. data/gemfiles/activerecord_4.2.gemfile +0 -19
  35. data/gemfiles/activerecord_5.0.gemfile +0 -19
  36. data/gemfiles/activerecord_5.1.gemfile +0 -19
  37. data/lib/with_advisory_lock/mysql_no_nesting.rb +0 -20
  38. data/lib/with_advisory_lock/nested_advisory_lock_error.rb +0 -14
  39. data/test/database.yml +0 -17
  40. data/test/minitest_helper.rb +0 -40
  41. 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: 784812e261b5a57a5d3da8c878e536949245b29372b40b2baf15a52d7c65b854
4
+ data.tar.gz: ba2f6fc8bf78c89d56635c4f56500117f79a443404d3574d8b54b2a45d1811de
5
5
  SHA512:
6
- metadata.gz: be5a906b9740506447bfcdb1928cd5473add67e0a48acd32f8a26ca4671be49b34ca5eca987df2f8e6f29a0a3dbb0bccc3321061d9f1e5827a0ca4ef44341124
7
- data.tar.gz: 124358b251ffd9cc066d54aebfeeca97fb0db88ae18293fb7e0169ceb13960d298e72c66e9d5fbd79f0cbda5360d2fdb2e609866ac82eeb26b1eab6ead103daf
6
+ metadata.gz: bb7dc11c1f206639943daa363a4d917bfe93792e7db764c7860ef68151e99defaa03f58640fae0480b566e3d4a0697695f5b60ae8af09838bb176979c328d8ae
7
+ data.tar.gz: 10132a0c47ed8070a0661a7b6ae0409be76ef14693be65c12a8e8cdd9c5a506ba25c60bda264f7c60bd92952e4cd4c5513a5e566a3e0d757f0ab6fc4311ffcd5
@@ -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
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
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,14 @@
1
1
  ## Changelog
2
2
 
3
+ ### 5.0.0
4
+ - Drop support for EOL rubies and activerecord (ruby below 2.7 and activerecord below 6.1).
5
+ - Allow lock name to be integer
6
+ - Jruby support
7
+ - Truffleruby support
8
+ - Add `with_advisory_lock!`, which raises an error if the lock acquisition fails
9
+ - Add `disable_query_cache` option to `with_advisory_lock`
10
+ - Drop support for mysql < 5.7.5
11
+
3
12
  ### 4.6.0
4
13
 
5
14
  - 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
@@ -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,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
 
3
5
  module WithAdvisoryLock
4
6
  module Concern
5
7
  extend ActiveSupport::Concern
6
- delegate :with_advisory_lock, :advisory_lock_exists?, to: 'self.class'
8
+ delegate :with_advisory_lock, :with_advisory_lock!, :advisory_lock_exists?, to: 'self.class'
7
9
 
8
- module ClassMethods
10
+ class_methods do
9
11
  def with_advisory_lock(lock_name, options = {}, &block)
10
12
  result = with_advisory_lock_result(lock_name, options, &block)
11
13
  result.lock_was_acquired? ? result.result : false
12
14
  end
13
15
 
16
+ def with_advisory_lock!(lock_name, options = {}, &block)
17
+ result = with_advisory_lock_result(lock_name, options, &block)
18
+ unless result.lock_was_acquired?
19
+ raise WithAdvisoryLock::FailedToAcquireLock, lock_name
20
+ end
21
+
22
+ result.result
23
+ end
24
+
14
25
  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)
26
+ impl = impl_class.new(connection, lock_name, options)
17
27
  impl.with_advisory_lock_if_needed(&block)
18
28
  end
19
29
 
@@ -29,22 +39,12 @@ module WithAdvisoryLock
29
39
 
30
40
  private
31
41
 
32
- def impl_class(options = nil)
42
+ def impl_class
33
43
  adapter = WithAdvisoryLock::DatabaseAdapterSupport.new(connection)
34
44
  if adapter.postgresql?
35
45
  WithAdvisoryLock::PostgreSQL
36
46
  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
47
+ WithAdvisoryLock::MySQL
48
48
  else
49
49
  WithAdvisoryLock::Flock
50
50
  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,7 @@
1
+ module WithAdvisoryLock
2
+ class FailedToAcquireLock < StandardError
3
+ def initialize(lock_name)
4
+ super("Failed to acquire lock #{lock_name}")
5
+ end
6
+ end
7
+ 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.0.0')
3
5
  end
@@ -1,5 +1,6 @@
1
1
  require 'with_advisory_lock/version'
2
2
  require 'active_support'
3
+ require_relative 'with_advisory_lock/failed_to_acquire_lock'
3
4
 
4
5
  module WithAdvisoryLock
5
6
  extend ActiveSupport::Autoload
@@ -9,8 +10,6 @@ module WithAdvisoryLock
9
10
  autoload :DatabaseAdapterSupport
10
11
  autoload :Flock
11
12
  autoload :MySQL, 'with_advisory_lock/mysql'
12
- autoload :MySQLNoNesting, 'with_advisory_lock/mysql_no_nesting'
13
- autoload :NestedAdvisoryLockError
14
13
  autoload :PostgreSQL, 'with_advisory_lock/postgresql'
15
14
  end
16
15
 
data/test/concern_test.rb CHANGED
@@ -1,20 +1,33 @@
1
- require 'minitest_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe "with_advisory_lock.concern" do
4
- it "adds with_advisory_lock to ActiveRecord classes" do
5
- assert Tag.respond_to?(:with_advisory_lock)
3
+ require 'test_helper'
4
+
5
+ class WithAdvisoryLockConcernTest < GemTestCase
6
+ test 'adds with_advisory_lock to ActiveRecord classes' do
7
+ assert_respond_to(Tag, :with_advisory_lock)
6
8
  end
7
9
 
8
- it "adds with_advisory_lock to ActiveRecord instances" do
9
- assert Label.new.respond_to?(:with_advisory_lock)
10
+ test 'adds with_advisory_lock to ActiveRecord instances' do
11
+ assert_respond_to(Label.new, :with_advisory_lock)
10
12
  end
11
13
 
12
- it "adds advisory_lock_exists? to ActiveRecord classes" do
13
- assert Tag.respond_to?(:advisory_lock_exists?)
14
+ test 'adds advisory_lock_exists? to ActiveRecord classes' do
15
+ assert_respond_to(Tag, :advisory_lock_exists?)
14
16
  end
15
17
 
16
- it "adds advisory_lock_exists? to ActiveRecord classes" do
17
- assert Label.new.respond_to?(:advisory_lock_exists?)
18
+ test 'adds advisory_lock_exists? to ActiveRecord instances' do
19
+ assert_respond_to(Label.new, :advisory_lock_exists?)
18
20
  end
21
+ end
19
22
 
23
+ class ActiveRecordQueryCacheTest < GemTestCase
24
+ test 'does not disable quary cache by default' do
25
+ ActiveRecord::Base.expects(:uncached).never
26
+ Tag.with_advisory_lock('lock') { Tag.first }
27
+ end
28
+
29
+ test 'can disable ActiveRecord query cache' do
30
+ ActiveRecord::Base.expects(:uncached).once
31
+ Tag.with_advisory_lock('a-lock', disable_query_cache: true) { Tag.first }
32
+ end
20
33
  end