with_advisory_lock 4.0.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.tool-versions +1 -0
- data/.travis.yml +23 -17
- data/Appraisals +11 -1
- data/CHANGELOG.md +122 -0
- data/README.md +74 -171
- data/gemfiles/activerecord_4.2.gemfile +1 -1
- data/gemfiles/activerecord_5.0.gemfile +1 -1
- data/gemfiles/activerecord_5.1.gemfile +1 -1
- data/gemfiles/activerecord_5.2.gemfile +1 -1
- data/gemfiles/activerecord_6.0.gemfile +19 -0
- data/lib/with_advisory_lock.rb +1 -0
- data/lib/with_advisory_lock/concern.rb +14 -3
- data/lib/with_advisory_lock/database_adapter_support.rb +45 -1
- data/lib/with_advisory_lock/mysql.rb +2 -12
- data/lib/with_advisory_lock/mysql_no_nesting.rb +20 -0
- data/lib/with_advisory_lock/version.rb +1 -1
- data/test/nesting_test.rb +37 -4
- metadata +7 -5
- data/.travis.install-mysql-5.7.sh +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bc4719c3f44e4cf1f219b582d21cf9757274ca1fcca6128c227798f32d4b589e
|
4
|
+
data.tar.gz: c63b8be1ee91b25a519b5a4d7d090ff98172d6d3a476899b55c6cd03bf77eda6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be5a906b9740506447bfcdb1928cd5473add67e0a48acd32f8a26ca4671be49b34ca5eca987df2f8e6f29a0a3dbb0bccc3321061d9f1e5827a0ca4ef44341124
|
7
|
+
data.tar.gz: 124358b251ffd9cc066d54aebfeeca97fb0db88ae18293fb7e0169ceb13960d298e72c66e9d5fbd79f0cbda5360d2fdb2e609866ac82eeb26b1eab6ead103daf
|
data/.gitignore
CHANGED
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 2.6.4
|
data/.travis.yml
CHANGED
@@ -1,32 +1,38 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
services:
|
4
|
+
- postgresql
|
5
|
+
- mysql
|
6
|
+
|
7
|
+
addons:
|
8
|
+
postgresql: "10"
|
5
9
|
|
6
10
|
rvm:
|
7
|
-
- 2.
|
8
|
-
- 2.
|
9
|
-
- 2.
|
10
|
-
- 2.2.10
|
11
|
+
- 2.6.4
|
12
|
+
- 2.5.6
|
13
|
+
- 2.4.7
|
11
14
|
|
12
15
|
gemfile:
|
16
|
+
- gemfiles/activerecord_6.0.gemfile
|
13
17
|
- gemfiles/activerecord_5.2.gemfile
|
14
18
|
- gemfiles/activerecord_5.1.gemfile
|
15
19
|
- gemfiles/activerecord_5.0.gemfile
|
16
20
|
- gemfiles/activerecord_4.2.gemfile
|
17
21
|
|
18
22
|
env:
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
23
33
|
|
24
34
|
before_install:
|
25
|
-
- gem install bundler
|
26
|
-
|
27
|
-
script: WITH_ADVISORY_LOCK_PREFIX=$TRAVIS_JOB_ID bundle exec rake --trace
|
28
|
-
|
29
|
-
before_script:
|
30
|
-
- bash .travis.install-mysql-5.7.sh
|
31
35
|
- mysql -e 'create database with_advisory_lock_test'
|
32
|
-
- psql -c 'create database with_advisory_lock_test' -U postgres
|
36
|
+
- psql -c 'create database with_advisory_lock_test' -U postgres
|
37
|
+
|
38
|
+
script: bundle exec rake --trace
|
data/Appraisals
CHANGED
@@ -3,17 +3,27 @@ appraise "activerecord-4.2" do
|
|
3
3
|
platforms :ruby do
|
4
4
|
gem "pg", "~> 0.21"
|
5
5
|
gem "mysql2", "< 0.5"
|
6
|
+
gem "sqlite3", "~> 1.3.6"
|
6
7
|
end
|
7
8
|
end
|
8
9
|
|
9
10
|
appraise "activerecord-5.0" do
|
10
11
|
gem "activerecord", "~> 5.0.0"
|
12
|
+
platforms :ruby do
|
13
|
+
gem "sqlite3", "~> 1.3.6"
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
17
|
appraise "activerecord-5.1" do
|
14
18
|
gem "activerecord", "~> 5.1.0"
|
19
|
+
gem "sqlite3", "~> 1.3.6"
|
15
20
|
end
|
16
21
|
|
17
22
|
appraise "activerecord-5.2" do
|
18
23
|
gem "activerecord", "~> 5.1.0"
|
19
|
-
|
24
|
+
gem "sqlite3", "~> 1.3.6"
|
25
|
+
end
|
26
|
+
|
27
|
+
appraise "activerecord-6.0" do
|
28
|
+
gem "activerecord", "~> 6.0.0"
|
29
|
+
end
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
## Changelog
|
2
|
+
|
3
|
+
### 4.6.0
|
4
|
+
|
5
|
+
- Support for ActiveRecord 6
|
6
|
+
- Add Support for nested locks in MySQL
|
7
|
+
|
8
|
+
### 4.0.0
|
9
|
+
|
10
|
+
- Drop support for unsupported versions of activerecord
|
11
|
+
- Drop support for unsupported versions of ruby
|
12
|
+
|
13
|
+
### 3.2.0
|
14
|
+
|
15
|
+
- [Joshua Flanagan](https://github.com/joshuaflanagan) [added a SQL comment to the lock query for PostgreSQL](https://github.com/ClosureTree/with_advisory_lock/pull/28). Thanks!
|
16
|
+
- [Fernando Luizão](https://github.com/fernandoluizao) found a spurious requirement for `thread_safe`. Thanks for the [fix](https://github.com/ClosureTree/with_advisory_lock/pull/27)!
|
17
|
+
|
18
|
+
### 3.1.1
|
19
|
+
|
20
|
+
- [Joel Turkel](https://github.com/jturkel) added `require 'active_support'` (it was required, but relied on downstream gems to pull in active_support before pulling in with_advisory_lock). Thanks!
|
21
|
+
|
22
|
+
### 3.1.0
|
23
|
+
|
24
|
+
- [Jason Weathered](https://github.com/jasoncodes) Added new shared and transaction-level lock options ([Pull request 21](https://github.com/ClosureTree/with_advisory_lock/pull/21)). Thanks!
|
25
|
+
- Added ActiveRecord 5.0 to build matrix. Dropped 3.2, 4.0, and 4.1 (which no longer get security updates: http://rubyonrails.org/security/)
|
26
|
+
- Replaced ruby 1.9 and 2.0 (both EOL) with ruby 2.2 and 2.3 (see https://www.ruby-lang.org/en/downloads/)
|
27
|
+
|
28
|
+
### 3.0.0
|
29
|
+
|
30
|
+
- Added jruby/PostgreSQL support for Rails 4.x
|
31
|
+
- Reworked threaded tests to allow jruby tests to pass
|
32
|
+
|
33
|
+
#### API changes
|
34
|
+
|
35
|
+
- `yield_with_lock_and_timeout` and `yield_with_lock` now return instances of
|
36
|
+
`WithAdvisoryLock::Result`, so blocks that return `false` are not misinterpreted
|
37
|
+
as a failure to lock. As this changes the interface (albeit internal methods), the major version
|
38
|
+
number was incremented.
|
39
|
+
- `with_advisory_lock_result` was introduced, which clarifies whether the lock was acquired
|
40
|
+
versus the yielded block returned false.
|
41
|
+
|
42
|
+
### 2.0.0
|
43
|
+
|
44
|
+
- Lock timeouts of 0 now attempt the lock once, as per suggested by
|
45
|
+
[Jon Leighton](https://github.com/jonleighton) and implemented by
|
46
|
+
[Abdelkader Boudih](https://github.com/seuros). Thanks to both of you!
|
47
|
+
- [Pull request 11](https://github.com/ClosureTree/with_advisory_lock/pull/11)
|
48
|
+
fixed a downstream issue with jruby support! Thanks, [Aaron Todd](https://github.com/ozzyaaron)!
|
49
|
+
- Added Travis tests for jruby
|
50
|
+
- Dropped support for Rails 3.0, 3.1, and Ruby 1.8.7, as they are no longer
|
51
|
+
receiving security patches. See http://rubyonrails.org/security/ for more information.
|
52
|
+
This required the major version bump.
|
53
|
+
- Refactored `advisory_lock_exists?` to use existing functionality
|
54
|
+
- Fixed sqlite's implementation so parallel tests could be run against it
|
55
|
+
|
56
|
+
### 1.0.0
|
57
|
+
|
58
|
+
- Releasing 1.0.0. The interface will be stable.
|
59
|
+
- Added `advisory_lock_exists?`. Thanks, [Sean Devine](https://github.com/barelyknown), for the
|
60
|
+
great pull request!
|
61
|
+
- Added Travis test for Rails 4.1
|
62
|
+
|
63
|
+
### 0.0.10
|
64
|
+
|
65
|
+
- Explicitly added MIT licensing to the gemspec.
|
66
|
+
|
67
|
+
### 0.0.9
|
68
|
+
|
69
|
+
- Merged in Postgis Adapter Support to address [issue 7](https://github.com/ClosureTree/with_advisory_lock/issues/7)
|
70
|
+
Thanks for the pull request, [Abdelkader Boudih](https://github.com/seuros)!
|
71
|
+
- The database switching code had to be duplicated by [Closure Tree](https://github.com/ClosureTree/closure_tree),
|
72
|
+
so I extracted a new `WithAdvisoryLock::DatabaseAdapterSupport` one-trick pony.
|
73
|
+
- Builds were failing on Travis, so I introduced a global lock prefix that can be set with the
|
74
|
+
`WITH_ADVISORY_LOCK_PREFIX` environment variable. I'm not going to advertise this feature yet.
|
75
|
+
It's a secret. Only you and I know, now. _shhh_
|
76
|
+
|
77
|
+
### 0.0.8
|
78
|
+
|
79
|
+
- Addressed [issue 5](https://github.com/ClosureTree/with_advisory_lock/issues/5) by
|
80
|
+
using a deterministic hash for Postgresql + MRI >= 1.9.
|
81
|
+
Thanks for the pull request, [Joel Turkel](https://github.com/jturkel)!
|
82
|
+
- Addressed [issue 2](https://github.com/ClosureTree/with_advisory_lock/issues/2) by
|
83
|
+
using a cache-busting query for MySQL and Postgres to deal with AR value caching bug.
|
84
|
+
Thanks for the pull request, [Jaime Giraldo](https://github.com/sposmen)!
|
85
|
+
- Addressed [issue 4](https://github.com/ClosureTree/with_advisory_lock/issues/4) by
|
86
|
+
adding support for `em-postgresql-adapter`.
|
87
|
+
Thanks, [lestercsp](https://github.com/lestercsp)!
|
88
|
+
|
89
|
+
(Hey, github—your notifications are WAY too easy to ignore!)
|
90
|
+
|
91
|
+
### 0.0.7
|
92
|
+
|
93
|
+
- Added Travis tests for Rails 3.0, 3.1, 3.2, and 4.0
|
94
|
+
- Fixed MySQL bug with select_value returning a string instead of an integer when using AR 3.0.x
|
95
|
+
|
96
|
+
### 0.0.6
|
97
|
+
|
98
|
+
- Only require ActiveRecord >= 3.0.x
|
99
|
+
- Fixed MySQL error reporting
|
100
|
+
|
101
|
+
### 0.0.5
|
102
|
+
|
103
|
+
- Asking for the currently acquired advisory lock doesn't re-ask for the lock now.
|
104
|
+
- Introduced NestedAdvisoryLockError when asking for different, nested advisory locksMySQL
|
105
|
+
|
106
|
+
### 0.0.4
|
107
|
+
|
108
|
+
- Moved require into on_load, which should speed loading when AR doesn't have to spin up
|
109
|
+
|
110
|
+
### 0.0.3
|
111
|
+
|
112
|
+
- Fought with ActiveRecord 3.0.x and 3.1.x. You don't want them if you use threads—they fail
|
113
|
+
predictably.
|
114
|
+
|
115
|
+
### 0.0.2
|
116
|
+
|
117
|
+
- Added warning log message for nested MySQL lock calls
|
118
|
+
- Randomized lock wait time, which can help ameliorate lock contention
|
119
|
+
|
120
|
+
### 0.0.1
|
121
|
+
|
122
|
+
- First whack
|
data/README.md
CHANGED
@@ -1,22 +1,27 @@
|
|
1
1
|
# with_advisory_lock
|
2
2
|
|
3
|
-
Adds advisory locking (mutexes) to ActiveRecord 4.2 and
|
4
|
-
|
5
|
-
|
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
|
5
|
+
[MySQL](https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_get-lock)
|
6
|
+
or
|
7
|
+
[PostgreSQL](https://www.postgresql.org/docs/current/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS).
|
6
8
|
SQLite resorts to file locking.
|
7
9
|
|
8
|
-
[](
|
10
|
+
[](https://travis-ci.org/ClosureTree/with_advisory_lock)
|
9
11
|
[](https://badge.fury.io/rb/with_advisory_lock)
|
10
12
|
|
11
13
|
## What's an "Advisory Lock"?
|
12
14
|
|
13
|
-
An advisory lock is a [mutex](
|
14
|
-
processes run some process at the same time. When the
|
15
|
-
server, as long as it isn't SQLite,
|
15
|
+
An advisory lock is a [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion)
|
16
|
+
used to ensure no two processes run some process at the same time. When the
|
17
|
+
advisory lock is powered by your database server, as long as it isn't SQLite,
|
18
|
+
your mutex spans hosts.
|
16
19
|
|
17
20
|
## Usage
|
18
21
|
|
19
|
-
|
22
|
+
This gem automatically includes the `WithAdvisoryLock` module in all of your
|
23
|
+
ActiveRecord models. Here's an example of how to use it where `User` is an
|
24
|
+
ActiveRecord model, and `lock_name` is some string:
|
20
25
|
|
21
26
|
```ruby
|
22
27
|
User.with_advisory_lock(lock_name) do
|
@@ -32,58 +37,65 @@ end
|
|
32
37
|
|
33
38
|
### Lock wait timeouts
|
34
39
|
|
35
|
-
|
36
|
-
|
40
|
+
`with_advisory_lock` takes an options hash as the second parameter. The
|
41
|
+
`timeout_seconds` option defaults to `nil`, which means wait indefinitely for
|
42
|
+
the lock.
|
37
43
|
|
38
44
|
A value of zero will try the lock only once. If the lock is acquired, the block
|
39
|
-
will be yielded to. If the lock is currently being held, the block will not be
|
45
|
+
will be yielded to. If the lock is currently being held, the block will not be
|
46
|
+
called.
|
40
47
|
|
41
|
-
Note that if a non-nil value is provided for `timeout_seconds`, the block will
|
42
|
-
the lock cannot be acquired within that time-frame.
|
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.
|
43
50
|
|
44
|
-
For backwards compatability, the timeout value can be specified directly as the
|
51
|
+
For backwards compatability, the timeout value can be specified directly as the
|
52
|
+
second parameter.
|
45
53
|
|
46
54
|
### Shared locks
|
47
55
|
|
48
|
-
The
|
49
|
-
Setting
|
50
|
-
as long as they are all shared locks.
|
56
|
+
The `shared` option defaults to `false` which means an exclusive lock will be
|
57
|
+
obtained. Setting `shared` to `true` will allow locks to be obtained by multiple
|
58
|
+
actors as long as they are all shared locks.
|
51
59
|
|
52
60
|
Note: MySQL does not support shared locks.
|
53
61
|
|
54
62
|
### Transaction-level locks
|
55
63
|
|
56
|
-
PostgreSQL supports transaction-level locks which remain held until the
|
57
|
-
You can enable this by setting the
|
64
|
+
PostgreSQL supports transaction-level locks which remain held until the
|
65
|
+
transaction completes. You can enable this by setting the `transaction` option
|
66
|
+
to `true`.
|
58
67
|
|
59
|
-
Note: transaction-level locks will not be reflected by `.current_advisory_lock`
|
68
|
+
Note: transaction-level locks will not be reflected by `.current_advisory_lock`
|
69
|
+
when the block has returned.
|
60
70
|
|
61
71
|
### Return values
|
62
72
|
|
63
|
-
The return value of `with_advisory_lock_result` is a `WithAdvisoryLock::Result`
|
64
|
-
which has a `lock_was_acquired?` method and a `result` accessor
|
65
|
-
the returned value of the given block. If your block may
|
66
|
-
this method.
|
67
|
-
|
68
|
-
The return value of
|
69
|
-
if the lock was able to be acquired and the block yielded, or
|
70
|
-
a timeout_seconds value and the lock was not able to be acquired in
|
73
|
+
The return value of `with_advisory_lock_result` is a `WithAdvisoryLock::Result`
|
74
|
+
instance, which has a `lock_was_acquired?` method and a `result` accessor
|
75
|
+
method, which is the returned value of the given block. If your block may
|
76
|
+
validly return false, you should use this method.
|
77
|
+
|
78
|
+
The return value of `with_advisory_lock` will be the result of the yielded
|
79
|
+
block, if the lock was able to be acquired and the block yielded, or `false`, if
|
80
|
+
you provided a timeout_seconds value and the lock was not able to be acquired in
|
81
|
+
time.
|
71
82
|
|
72
83
|
### Testing for the current lock status
|
73
84
|
|
74
|
-
If you needed to check if the advisory lock is currently being held, you can
|
75
|
-
|
76
|
-
test for the lock, and the time you try to acquire the
|
85
|
+
If you needed to check if the advisory lock is currently being held, you can
|
86
|
+
call `Tag.advisory_lock_exists?("foo")`, but realize the lock can be acquired
|
87
|
+
between the time you test for the lock, and the time you try to acquire the
|
88
|
+
lock.
|
77
89
|
|
78
|
-
If you want to see if the current Thread is holding a lock, you can call
|
79
|
-
which will return the name of the current lock. If
|
80
|
-
|
90
|
+
If you want to see if the current Thread is holding a lock, you can call
|
91
|
+
`Tag.current_advisory_lock` which will return the name of the current lock. If
|
92
|
+
no lock is currently held, `.current_advisory_lock` returns `nil`.
|
81
93
|
|
82
94
|
## Installation
|
83
95
|
|
84
96
|
Add this line to your application's Gemfile:
|
85
97
|
|
86
|
-
```
|
98
|
+
```ruby
|
87
99
|
gem 'with_advisory_lock'
|
88
100
|
```
|
89
101
|
|
@@ -93,27 +105,31 @@ And then execute:
|
|
93
105
|
|
94
106
|
## Lock Types
|
95
107
|
|
96
|
-
First off, know that there are **lots** of different kinds of locks available to
|
97
|
-
finest-grain lock that ensures correctness.** If you choose a
|
98
|
-
unnecessarily blocking other processes.
|
108
|
+
First off, know that there are **lots** of different kinds of locks available to
|
109
|
+
you. **Pick the finest-grain lock that ensures correctness.** If you choose a
|
110
|
+
lock that is too coarse, you are unnecessarily blocking other processes.
|
99
111
|
|
100
112
|
### Advisory locks
|
101
|
-
|
102
|
-
|
113
|
+
|
114
|
+
These are named mutexes that are inherently "application level"—it is up to the
|
115
|
+
application to acquire, run a critical code section, and release the advisory
|
116
|
+
lock.
|
103
117
|
|
104
118
|
### Row-level locks
|
119
|
+
|
105
120
|
Whether [optimistic](http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html)
|
106
121
|
or [pessimistic](http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html),
|
107
122
|
row-level locks prevent concurrent modification to a given model.
|
108
123
|
|
109
124
|
**If you're building a
|
110
|
-
[CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete)
|
111
|
-
most commonly used lock.**
|
125
|
+
[CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete)
|
126
|
+
application, this will be your most commonly used lock.**
|
112
127
|
|
113
128
|
### Table-level locks
|
114
129
|
|
115
|
-
Provided through something like the
|
116
|
-
gem, these prevent
|
130
|
+
Provided through something like the
|
131
|
+
[monogamy](https://github.com/ClosureTree/monogamy) gem, these prevent
|
132
|
+
concurrent access to **any instance of a model**. Their coarseness means they
|
117
133
|
aren't going to be commonly applicable, and they can be a source of
|
118
134
|
[deadlocks](http://en.wikipedia.org/wiki/Deadlock).
|
119
135
|
|
@@ -125,26 +141,31 @@ Advisory locks with MySQL and PostgreSQL ignore database transaction boundaries.
|
|
125
141
|
|
126
142
|
You will want to wrap your block within a transaction to ensure consistency.
|
127
143
|
|
128
|
-
### MySQL doesn't support nesting
|
144
|
+
### MySQL < 5.7.5 doesn't support nesting
|
145
|
+
|
146
|
+
With MySQL < 5.7.5, if you ask for a _different_ advisory lock within
|
147
|
+
a `with_advisory_lock` block, you will be releasing the parent lock (!!!). A
|
148
|
+
`NestedAdvisoryLockError`will be raised in this case. If you ask for the same
|
149
|
+
lock name, `with_advisory_lock` won't ask for the lock again, and the block
|
150
|
+
given will be yielded to.
|
129
151
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
lock again, and the block given will be yielded to.
|
152
|
+
This is not an issue in MySQL >= 5.7.5, and no error will be raised for nested
|
153
|
+
lock usage. You can override this by passing `force_nested_lock_support: true`
|
154
|
+
or `force_nested_lock_support: false` to the `with_advisory_lock` options.
|
134
155
|
|
135
156
|
### Is clustered MySQL supported?
|
136
157
|
|
137
158
|
[No.](https://github.com/ClosureTree/with_advisory_lock/issues/16)
|
138
159
|
|
139
|
-
### There are many
|
160
|
+
### There are many `lock-*` files in my project directory after test runs
|
140
161
|
|
141
162
|
This is expected if you aren't using MySQL or Postgresql for your tests.
|
142
163
|
See [issue 3](https://github.com/ClosureTree/with_advisory_lock/issues/3).
|
143
164
|
|
144
|
-
SQLite doesn't have advisory locks, so we resort to file locking, which will
|
145
|
-
if the
|
165
|
+
SQLite doesn't have advisory locks, so we resort to file locking, which will
|
166
|
+
only work if the `FLOCK_DIR` is set consistently for all ruby processes.
|
146
167
|
|
147
|
-
In your
|
168
|
+
In your `spec_helper.rb` or `minitest_helper.rb`, add a `before` and `after` block:
|
148
169
|
|
149
170
|
```ruby
|
150
171
|
before do
|
@@ -155,121 +176,3 @@ after do
|
|
155
176
|
FileUtils.remove_entry_secure ENV['FLOCK_DIR']
|
156
177
|
end
|
157
178
|
```
|
158
|
-
|
159
|
-
## Changelog
|
160
|
-
|
161
|
-
### 4.0.0
|
162
|
-
|
163
|
-
* Drop support for unsupported versions of activerecord
|
164
|
-
* Drop support for unsupported versions of ruby
|
165
|
-
|
166
|
-
### 3.2.0
|
167
|
-
|
168
|
-
* [Joshua Flanagan](https://github.com/joshuaflanagan) [added a SQL comment to the lock query for PostgreSQL](https://github.com/ClosureTree/with_advisory_lock/pull/28). Thanks!
|
169
|
-
* [Fernando Luizão](https://github.com/fernandoluizao) found a spurious requirement for `thread_safe`. Thanks for the [fix](https://github.com/ClosureTree/with_advisory_lock/pull/27)!
|
170
|
-
|
171
|
-
### 3.1.1
|
172
|
-
|
173
|
-
* [Joel Turkel](https://github.com/jturkel) added `require 'active_support'` (it was required, but relied on downstream gems to pull in active_support before pulling in with_advisory_lock). Thanks!
|
174
|
-
|
175
|
-
### 3.1.0
|
176
|
-
|
177
|
-
* [Jason Weathered](https://github.com/jasoncodes) Added new shared and transaction-level lock options ([Pull request 21](https://github.com/ClosureTree/with_advisory_lock/pull/21)). Thanks!
|
178
|
-
* Added ActiveRecord 5.0 to build matrix. Dropped 3.2, 4.0, and 4.1 (which no longer get security updates: http://rubyonrails.org/security/)
|
179
|
-
* Replaced ruby 1.9 and 2.0 (both EOL) with ruby 2.2 and 2.3 (see https://www.ruby-lang.org/en/downloads/)
|
180
|
-
|
181
|
-
### 3.0.0
|
182
|
-
|
183
|
-
* Added jruby/PostgreSQL support for Rails 4.x
|
184
|
-
* Reworked threaded tests to allow jruby tests to pass
|
185
|
-
|
186
|
-
#### API changes
|
187
|
-
|
188
|
-
* `yield_with_lock_and_timeout` and `yield_with_lock` now return instances of
|
189
|
-
`WithAdvisoryLock::Result`, so blocks that return `false` are not misinterpreted
|
190
|
-
as a failure to lock. As this changes the interface (albeit internal methods), the major version
|
191
|
-
number was incremented.
|
192
|
-
* `with_advisory_lock_result` was introduced, which clarifies whether the lock was acquired
|
193
|
-
versus the yielded block returned false.
|
194
|
-
|
195
|
-
### 2.0.0
|
196
|
-
|
197
|
-
* Lock timeouts of 0 now attempt the lock once, as per suggested by
|
198
|
-
[Jon Leighton](https://github.com/jonleighton) and implemented by
|
199
|
-
[Abdelkader Boudih](https://github.com/seuros). Thanks to both of you!
|
200
|
-
* [Pull request 11](https://github.com/ClosureTree/with_advisory_lock/pull/11)
|
201
|
-
fixed a downstream issue with jruby support! Thanks, [Aaron Todd](https://github.com/ozzyaaron)!
|
202
|
-
* Added Travis tests for jruby
|
203
|
-
* Dropped support for Rails 3.0, 3.1, and Ruby 1.8.7, as they are no longer
|
204
|
-
receiving security patches. See http://rubyonrails.org/security/ for more information.
|
205
|
-
This required the major version bump.
|
206
|
-
* Refactored `advisory_lock_exists?` to use existing functionality
|
207
|
-
* Fixed sqlite's implementation so parallel tests could be run against it
|
208
|
-
|
209
|
-
### 1.0.0
|
210
|
-
|
211
|
-
* Releasing 1.0.0. The interface will be stable.
|
212
|
-
* Added ```advisory_lock_exists?```. Thanks, [Sean Devine](https://github.com/barelyknown), for the
|
213
|
-
great pull request!
|
214
|
-
* Added Travis test for Rails 4.1
|
215
|
-
|
216
|
-
### 0.0.10
|
217
|
-
|
218
|
-
* Explicitly added MIT licensing to the gemspec.
|
219
|
-
|
220
|
-
### 0.0.9
|
221
|
-
|
222
|
-
* Merged in Postgis Adapter Support to address [issue 7](https://github.com/ClosureTree/with_advisory_lock/issues/7)
|
223
|
-
Thanks for the pull request, [Abdelkader Boudih](https://github.com/seuros)!
|
224
|
-
* The database switching code had to be duplicated by [Closure Tree](https://github.com/ClosureTree/closure_tree),
|
225
|
-
so I extracted a new ```WithAdvisoryLock::DatabaseAdapterSupport``` one-trick pony.
|
226
|
-
* Builds were failing on Travis, so I introduced a global lock prefix that can be set with the
|
227
|
-
```WITH_ADVISORY_LOCK_PREFIX``` environment variable. I'm not going to advertise this feature yet.
|
228
|
-
It's a secret. Only you and I know, now. *shhh*
|
229
|
-
|
230
|
-
### 0.0.8
|
231
|
-
|
232
|
-
* Addressed [issue 5](https://github.com/ClosureTree/with_advisory_lock/issues/5) by
|
233
|
-
using a deterministic hash for Postgresql + MRI >= 1.9.
|
234
|
-
Thanks for the pull request, [Joel Turkel](https://github.com/jturkel)!
|
235
|
-
* Addressed [issue 2](https://github.com/ClosureTree/with_advisory_lock/issues/2) by
|
236
|
-
using a cache-busting query for MySQL and Postgres to deal with AR value caching bug.
|
237
|
-
Thanks for the pull request, [Jaime Giraldo](https://github.com/sposmen)!
|
238
|
-
* Addressed [issue 4](https://github.com/ClosureTree/with_advisory_lock/issues/4) by
|
239
|
-
adding support for ```em-postgresql-adapter```.
|
240
|
-
Thanks, [lestercsp](https://github.com/lestercsp)!
|
241
|
-
|
242
|
-
(Hey, github—your notifications are WAY too easy to ignore!)
|
243
|
-
|
244
|
-
### 0.0.7
|
245
|
-
|
246
|
-
* Added Travis tests for Rails 3.0, 3.1, 3.2, and 4.0
|
247
|
-
* Fixed MySQL bug with select_value returning a string instead of an integer when using AR 3.0.x
|
248
|
-
|
249
|
-
### 0.0.6
|
250
|
-
|
251
|
-
* Only require ActiveRecord >= 3.0.x
|
252
|
-
* Fixed MySQL error reporting
|
253
|
-
|
254
|
-
### 0.0.5
|
255
|
-
|
256
|
-
* Asking for the currently acquired advisory lock doesn't re-ask for the lock now.
|
257
|
-
* Introduced NestedAdvisoryLockError when asking for different, nested advisory locksMySQL
|
258
|
-
|
259
|
-
### 0.0.4
|
260
|
-
|
261
|
-
* Moved require into on_load, which should speed loading when AR doesn't have to spin up
|
262
|
-
|
263
|
-
### 0.0.3
|
264
|
-
|
265
|
-
* Fought with ActiveRecord 3.0.x and 3.1.x. You don't want them if you use threads—they fail
|
266
|
-
predictably.
|
267
|
-
|
268
|
-
### 0.0.2
|
269
|
-
|
270
|
-
* Added warning log message for nested MySQL lock calls
|
271
|
-
* Randomized lock wait time, which can help ameliorate lock contention
|
272
|
-
|
273
|
-
### 0.0.1
|
274
|
-
|
275
|
-
* First whack
|
@@ -0,0 +1,19 @@
|
|
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: "../"
|
data/lib/with_advisory_lock.rb
CHANGED
@@ -9,6 +9,7 @@ module WithAdvisoryLock
|
|
9
9
|
autoload :DatabaseAdapterSupport
|
10
10
|
autoload :Flock
|
11
11
|
autoload :MySQL, 'with_advisory_lock/mysql'
|
12
|
+
autoload :MySQLNoNesting, 'with_advisory_lock/mysql_no_nesting'
|
12
13
|
autoload :NestedAdvisoryLockError
|
13
14
|
autoload :PostgreSQL, 'with_advisory_lock/postgresql'
|
14
15
|
end
|
@@ -12,7 +12,8 @@ module WithAdvisoryLock
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def with_advisory_lock_result(lock_name, options = {}, &block)
|
15
|
-
|
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)
|
16
17
|
impl.with_advisory_lock_if_needed(&block)
|
17
18
|
end
|
18
19
|
|
@@ -28,12 +29,22 @@ module WithAdvisoryLock
|
|
28
29
|
|
29
30
|
private
|
30
31
|
|
31
|
-
def impl_class
|
32
|
+
def impl_class(options = nil)
|
32
33
|
adapter = WithAdvisoryLock::DatabaseAdapterSupport.new(connection)
|
33
34
|
if adapter.postgresql?
|
34
35
|
WithAdvisoryLock::PostgreSQL
|
35
36
|
elsif adapter.mysql?
|
36
|
-
|
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
|
37
48
|
else
|
38
49
|
WithAdvisoryLock::Flock
|
39
50
|
end
|
@@ -1,13 +1,57 @@
|
|
1
1
|
module WithAdvisoryLock
|
2
2
|
class DatabaseAdapterSupport
|
3
|
+
# Caches nested lock support by MySQL reported version
|
4
|
+
@@mysql_nl_cache = {}
|
5
|
+
@@mysql_nl_cache_mutex = Mutex.new
|
6
|
+
|
3
7
|
def initialize(connection)
|
4
|
-
@
|
8
|
+
@connection = connection
|
9
|
+
@sym_name = connection.adapter_name.downcase.to_sym
|
5
10
|
end
|
6
11
|
|
7
12
|
def mysql?
|
8
13
|
%i[mysql mysql2].include? @sym_name
|
9
14
|
end
|
10
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
|
+
|
11
55
|
def postgresql?
|
12
56
|
%i[postgresql empostgresql postgis].include? @sym_name
|
13
57
|
end
|
@@ -1,13 +1,8 @@
|
|
1
1
|
module WithAdvisoryLock
|
2
|
+
# MySQL > 5.7.5 supports nested locks
|
2
3
|
class MySQL < Base
|
3
|
-
# See
|
4
|
+
# See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
|
4
5
|
def try_lock
|
5
|
-
unless lock_stack.empty?
|
6
|
-
raise NestedAdvisoryLockError.new(
|
7
|
-
"MySQL doesn't support nested Advisory Locks",
|
8
|
-
lock_stack.dup
|
9
|
-
)
|
10
|
-
end
|
11
6
|
raise ArgumentError, 'shared locks are not supported on MySQL' if shared
|
12
7
|
if transaction
|
13
8
|
raise ArgumentError, 'transaction level locks are not supported on MySQL'
|
@@ -24,11 +19,6 @@ module WithAdvisoryLock
|
|
24
19
|
connection.select_value(sql).to_i > 0
|
25
20
|
end
|
26
21
|
|
27
|
-
# MySQL doesn't support nested locks:
|
28
|
-
def already_locked?
|
29
|
-
lock_stack.last == lock_stack_item
|
30
|
-
end
|
31
|
-
|
32
22
|
# MySQL wants a string as the lock key.
|
33
23
|
def quoted_lock_str
|
34
24
|
connection.quote(lock_str)
|
@@ -0,0 +1,20 @@
|
|
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
|
data/test/nesting_test.rb
CHANGED
@@ -25,8 +25,8 @@ describe "lock nesting" do
|
|
25
25
|
impl.lock_stack.must_be_empty
|
26
26
|
end
|
27
27
|
|
28
|
-
it "raises errors with MySQL when acquiring nested lock" do
|
29
|
-
skip unless env_db == :mysql
|
28
|
+
it "raises errors with MySQL < 5.7.5 when acquiring nested lock" do
|
29
|
+
skip unless env_db == :mysql && ENV['MYSQL_VERSION'] != '5.7'
|
30
30
|
exc = proc {
|
31
31
|
Tag.with_advisory_lock("first") do
|
32
32
|
Tag.with_advisory_lock("second") do
|
@@ -36,8 +36,30 @@ describe "lock nesting" do
|
|
36
36
|
exc.lock_stack.map(&:name).must_equal %w(first)
|
37
37
|
end
|
38
38
|
|
39
|
-
it "
|
40
|
-
skip
|
39
|
+
it "does not raise errors with MySQL < 5.7.5 when acquiring nested error force enabled" do
|
40
|
+
skip unless env_db == :mysql && ENV['MYSQL_VERSION'] != '5.7'
|
41
|
+
impl = WithAdvisoryLock::Base.new(nil, nil, nil)
|
42
|
+
impl.lock_stack.must_be_empty
|
43
|
+
Tag.with_advisory_lock("first", force_nested_lock_support: true) do
|
44
|
+
impl.lock_stack.map(&:name).must_equal %w(first)
|
45
|
+
Tag.with_advisory_lock("second", force_nested_lock_support: true) do
|
46
|
+
impl.lock_stack.map(&:name).must_equal %w(first second)
|
47
|
+
Tag.with_advisory_lock("first", force_nested_lock_support: true) do
|
48
|
+
# Shouldn't ask for another lock:
|
49
|
+
impl.lock_stack.map(&:name).must_equal %w(first second)
|
50
|
+
Tag.with_advisory_lock("second", force_nested_lock_support: true) do
|
51
|
+
# Shouldn't ask for another lock:
|
52
|
+
impl.lock_stack.map(&:name).must_equal %w(first second)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
impl.lock_stack.map(&:name).must_equal %w(first)
|
57
|
+
end
|
58
|
+
impl.lock_stack.must_be_empty
|
59
|
+
end
|
60
|
+
|
61
|
+
it "supports nested advisory locks with !MySQL 5.6" do
|
62
|
+
skip if env_db == :mysql && ENV['MYSQL_VERSION'] != '5.7'
|
41
63
|
impl = WithAdvisoryLock::Base.new(nil, nil, nil)
|
42
64
|
impl.lock_stack.must_be_empty
|
43
65
|
Tag.with_advisory_lock("first") do
|
@@ -57,4 +79,15 @@ describe "lock nesting" do
|
|
57
79
|
end
|
58
80
|
impl.lock_stack.must_be_empty
|
59
81
|
end
|
82
|
+
|
83
|
+
it "raises with !MySQL 5.6 and nested error force disabled" do
|
84
|
+
skip unless env_db == :mysql && ENV['MYSQL_VERSION'] != '5.7'
|
85
|
+
exc = proc {
|
86
|
+
Tag.with_advisory_lock("first", force_nested_lock_support: false) do
|
87
|
+
Tag.with_advisory_lock("second", force_nested_lock_support: false) do
|
88
|
+
end
|
89
|
+
end
|
90
|
+
}.must_raise WithAdvisoryLock::NestedAdvisoryLockError
|
91
|
+
exc.lock_stack.map(&:name).must_equal %w(first)
|
92
|
+
end
|
60
93
|
end
|
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.
|
4
|
+
version: 4.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -116,9 +116,10 @@ extensions: []
|
|
116
116
|
extra_rdoc_files: []
|
117
117
|
files:
|
118
118
|
- ".gitignore"
|
119
|
-
- ".
|
119
|
+
- ".tool-versions"
|
120
120
|
- ".travis.yml"
|
121
121
|
- Appraisals
|
122
|
+
- CHANGELOG.md
|
122
123
|
- Gemfile
|
123
124
|
- LICENSE.txt
|
124
125
|
- README.md
|
@@ -127,12 +128,14 @@ files:
|
|
127
128
|
- gemfiles/activerecord_5.0.gemfile
|
128
129
|
- gemfiles/activerecord_5.1.gemfile
|
129
130
|
- gemfiles/activerecord_5.2.gemfile
|
131
|
+
- gemfiles/activerecord_6.0.gemfile
|
130
132
|
- lib/with_advisory_lock.rb
|
131
133
|
- lib/with_advisory_lock/base.rb
|
132
134
|
- lib/with_advisory_lock/concern.rb
|
133
135
|
- lib/with_advisory_lock/database_adapter_support.rb
|
134
136
|
- lib/with_advisory_lock/flock.rb
|
135
137
|
- lib/with_advisory_lock/mysql.rb
|
138
|
+
- lib/with_advisory_lock/mysql_no_nesting.rb
|
136
139
|
- lib/with_advisory_lock/nested_advisory_lock_error.rb
|
137
140
|
- lib/with_advisory_lock/postgresql.rb
|
138
141
|
- lib/with_advisory_lock/version.rb
|
@@ -168,8 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
171
|
- !ruby/object:Gem::Version
|
169
172
|
version: '0'
|
170
173
|
requirements: []
|
171
|
-
|
172
|
-
rubygems_version: 2.6.11
|
174
|
+
rubygems_version: 3.0.3
|
173
175
|
signing_key:
|
174
176
|
specification_version: 4
|
175
177
|
summary: Advisory locking for ActiveRecord
|
@@ -1,11 +0,0 @@
|
|
1
|
-
#!/usr/bin/env bash
|
2
|
-
if [[ "${MYSQL_VERSION}" == "5.7" ]]; then
|
3
|
-
sudo service mysql stop || echo "mysql not stopped"
|
4
|
-
sudo stop mysql-5.6 || echo "mysql-5.6 not stopped"
|
5
|
-
echo mysql-apt-config mysql-apt-config/select-server select mysql-5.7 | sudo debconf-set-selections
|
6
|
-
wget http://dev.mysql.com/get/mysql-apt-config_0.7.3-1_all.deb
|
7
|
-
sudo dpkg --install mysql-apt-config_0.7.3-1_all.deb
|
8
|
-
sudo apt-get update -q
|
9
|
-
sudo apt-get install -q -y --allow-unauthenticated -o Dpkg::Options::=--force-confnew mysql-server
|
10
|
-
sudo mysql_upgrade
|
11
|
-
fi
|