with_advisory_lock 4.0.0 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 82b0119f2158391cace745c0fa71a65497198de1
4
- data.tar.gz: eb7f626ed7db658000ad0db260a2692035ee4dc1
2
+ SHA256:
3
+ metadata.gz: bc4719c3f44e4cf1f219b582d21cf9757274ca1fcca6128c227798f32d4b589e
4
+ data.tar.gz: c63b8be1ee91b25a519b5a4d7d090ff98172d6d3a476899b55c6cd03bf77eda6
5
5
  SHA512:
6
- metadata.gz: efe96bf02dc09cf167ed2058c164405c4f6d6ae9e31442b46d9e9f5ceebc7add1850861d0b6bd311087a056cab5928ad27afa680a4b11884d1474bee4ae25269
7
- data.tar.gz: f9f753d910d24f6cb98fc91a47aed8dcccb82bf99f55ea193aceb8ffedf3c2732eef1c7945c64d1a5ac72aa27d89a0205307ddab72cce7113ca6c37c22496ac9
6
+ metadata.gz: be5a906b9740506447bfcdb1928cd5473add67e0a48acd32f8a26ca4671be49b34ca5eca987df2f8e6f29a0a3dbb0bccc3321061d9f1e5827a0ca4ef44341124
7
+ data.tar.gz: 124358b251ffd9cc066d54aebfeeca97fb0db88ae18293fb7e0169ceb13960d298e72c66e9d5fbd79f0cbda5360d2fdb2e609866ac82eeb26b1eab6ead103daf
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ spec/reports
17
17
  test/tmp
18
18
  test/version_tmp
19
19
  tmp
20
+ *.iml
@@ -0,0 +1 @@
1
+ ruby 2.6.4
@@ -1,32 +1,38 @@
1
1
  language: ruby
2
2
 
3
- dist: trusty
4
- sudo: required
3
+ services:
4
+ - postgresql
5
+ - mysql
6
+
7
+ addons:
8
+ postgresql: "10"
5
9
 
6
10
  rvm:
7
- - 2.5.1
8
- - 2.4.0
9
- - 2.3.3
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
- - DB=postgresql
20
- - DB=mysql
21
- - DB=mysql MYSQL_VERSION=5.7
22
- - DB=sqlite
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
- end
24
+ gem "sqlite3", "~> 1.3.6"
25
+ end
26
+
27
+ appraise "activerecord-6.0" do
28
+ gem "activerecord", "~> 6.0.0"
29
+ end
@@ -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 5.0, with ruby 2.4, 2.3 or 2.2, when used with
4
- [MySQL](http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock)
5
- or [PostgreSQL](http://www.postgresql.org/docs/9.3/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS).
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
- [![Build Status](https://api.travis-ci.org/ClosureTree/with_advisory_lock.svg?branch=master)](http://travis-ci.org/ClosureTree/with_advisory_lock)
10
+ [![Build Status](https://api.travis-ci.org/ClosureTree/with_advisory_lock.svg?branch=master)](https://travis-ci.org/ClosureTree/with_advisory_lock)
9
11
  [![Gem Version](https://badge.fury.io/rb/with_advisory_lock.svg)](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](http://en.wikipedia.org/wiki/Mutual_exclusion) used to ensure no two
14
- processes run some process at the same time. When the advisory lock is powered by your database
15
- server, as long as it isn't SQLite, your mutex spans hosts.
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
- Where ```User``` is an ActiveRecord model, and ```lock_name``` is some string:
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
- ```with_advisory_lock``` takes an options hash as the second parameter.
36
- The ```timeout_seconds``` option defaults to ```nil```, which means wait indefinitely for the lock.
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 called.
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 not be invoked if
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 second parameter.
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 ```shared``` option defaults to ```false``` which means an exclusive lock will be obtained.
49
- Setting ```shared``` to ```true``` will allow locks to be obtained by multiple actors
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 transaction completes.
57
- You can enable this by setting the ```transaction``` option to ```true```.
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` when the block has returned.
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` instance,
64
- which has a `lock_was_acquired?` method and a `result` accessor method, which is
65
- the returned value of the given block. If your block may validly return false, you should use
66
- this method.
67
-
68
- The return value of ```with_advisory_lock``` will be the result of the yielded block,
69
- if the lock was able to be acquired and the block yielded, or ```false```, if you provided
70
- a timeout_seconds value and the lock was not able to be acquired in time.
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 call
75
- ```Tag.advisory_lock_exists?("foo")```, but realize the lock can be acquired between the time you
76
- test for the lock, and the time you try to acquire the lock.
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 ```Tag.current_advisory_lock```
79
- which will return the name of the current lock. If no lock is currently held,
80
- ```.current_advisory_lock``` returns ```nil```.
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
- ``` ruby
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 you. **Pick the
97
- finest-grain lock that ensures correctness.** If you choose a lock that is too coarse, you are
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
- These are named mutexes that are inherently "application level"—it is up to the application
102
- to acquire, run a critical code section, and release the advisory lock.
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) application, this will be your
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 [monogamy](https://github.com/ClosureTree/monogamy)
116
- gem, these prevent concurrent access to **any instance of a model**. Their coarseness means they
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
- With MySQL (at least <= v5.5), if you ask for a *different* advisory lock within a ```with_advisory_lock``` block,
131
- you will be releasing the parent lock (!!!). A ```NestedAdvisoryLockError```will be raised
132
- in this case. If you ask for the same lock name, ```with_advisory_lock``` won't ask for the
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 ```lock-*``` files in my project directory after test runs
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 only work
145
- if the ```FLOCK_DIR``` is set consistently for all ruby processes.
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 ```spec_helper.rb``` or ```minitest_helper.rb```, add a ```before``` and ```after``` block:
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
@@ -7,7 +7,7 @@ gem "activerecord", "~> 4.2.0"
7
7
  platforms :ruby do
8
8
  gem "mysql2", "< 0.5"
9
9
  gem "pg", "~> 0.21"
10
- gem "sqlite3"
10
+ gem "sqlite3", "~> 1.3.6"
11
11
  end
12
12
 
13
13
  platforms :jruby do
@@ -7,7 +7,7 @@ gem "activerecord", "~> 5.0.0"
7
7
  platforms :ruby do
8
8
  gem "mysql2"
9
9
  gem "pg"
10
- gem "sqlite3"
10
+ gem "sqlite3", "~> 1.3.6"
11
11
  end
12
12
 
13
13
  platforms :jruby do
@@ -7,7 +7,7 @@ gem "activerecord", "~> 5.1.0"
7
7
  platforms :ruby do
8
8
  gem "mysql2"
9
9
  gem "pg"
10
- gem "sqlite3"
10
+ gem "sqlite3", "~> 1.3.6"
11
11
  end
12
12
 
13
13
  platforms :jruby do
@@ -7,7 +7,7 @@ gem "activerecord", "~> 5.1.0"
7
7
  platforms :ruby do
8
8
  gem "mysql2"
9
9
  gem "pg"
10
- gem "sqlite3"
10
+ gem "sqlite3", "~> 1.3.6"
11
11
  end
12
12
 
13
13
  platforms :jruby do
@@ -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: "../"
@@ -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
- impl = impl_class.new(connection, lock_name, options)
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
- WithAdvisoryLock::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
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
- @sym_name = connection.adapter_name.downcase.to_sym
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 http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock
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
@@ -1,3 +1,3 @@
1
1
  module WithAdvisoryLock
2
- VERSION = Gem::Version.new('4.0.0')
2
+ VERSION = Gem::Version.new('4.6.0')
3
3
  end
@@ -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 "supports nested advisory locks with !MySQL" do
40
- skip if env_db == :mysql
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.0.0
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: 2018-07-04 00:00:00.000000000 Z
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
- - ".travis.install-mysql-5.7.sh"
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
- rubyforge_project:
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