with_advisory_lock 1.0.0 → 2.0.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 -13
- data/.gitignore +1 -1
- data/.travis.yml +13 -44
- data/Appraisals +16 -0
- data/Gemfile +12 -0
- data/README.md +22 -4
- data/Rakefile +2 -2
- data/gemfiles/activerecord_3.2.gemfile +19 -0
- data/gemfiles/activerecord_4.0.gemfile +19 -0
- data/gemfiles/activerecord_4.1.gemfile +19 -0
- data/gemfiles/activerecord_edge.gemfile +20 -0
- data/lib/with_advisory_lock.rb +14 -1
- data/lib/with_advisory_lock/base.rb +18 -21
- data/lib/with_advisory_lock/concern.rb +8 -19
- data/lib/with_advisory_lock/database_adapter_support.rb +6 -0
- data/lib/with_advisory_lock/flock.rb +2 -2
- data/lib/with_advisory_lock/mysql.rb +11 -17
- data/lib/with_advisory_lock/postgresql.rb +15 -17
- data/lib/with_advisory_lock/version.rb +1 -1
- data/test/lock_test.rb +13 -6
- data/test/minitest_helper.rb +8 -5
- data/test/nesting_test.rb +2 -2
- data/test/parallelism_test.rb +81 -63
- data/test/test_models.rb +2 -2
- data/tests.sh +8 -7
- data/with_advisory_lock.gemspec +2 -4
- metadata +29 -59
- data/ci/Gemfile.rails-3.0.x +0 -5
- data/ci/Gemfile.rails-3.1.x +0 -4
- data/ci/Gemfile.rails-3.2.x +0 -4
- data/ci/Gemfile.rails-4.0.x +0 -4
- data/ci/Gemfile.rails-4.1.x +0 -4
- data/test/simple_parallel_test.rb +0 -37
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
OTdhNjkxNjZmODc5MjE2ODc2OWZmYzkzZDYxOTViNWY2ZWRlMDk2Nw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9a06363d16fb4769a2ee0486a8f0295dfe6e0c21
|
4
|
+
data.tar.gz: 3ce9cf5d558777ab689388fc086ce1fc2b18e1aa
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NWRlNGY3YTA0NmU3MjBiOGJiNjVkOGYxNWI3NjFmMjRlYmNhODM2NDg3N2U5
|
11
|
-
ZWM2NzNjYTczOWNiN2E2MzI4OWJiYWVlZWY1ZWEyNjM3MDhhNzY=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NmQyOWI0NmY4ZTJmNTFjY2YzNzlhNzM1ZDlmNWM0NWE5NjRiMWE2NDI3ZmU3
|
14
|
-
NTBmYzIwODZhZDUzMjQxYjM2NThkZDgxNGM1M2ZiODE4MjJhNmJiYTcwMTEw
|
15
|
-
MTEzMmMyYzQ0Yjk2YjRmMWZjMzAzZjAwNzlmZTJlYjQ1ODg2MWQ=
|
6
|
+
metadata.gz: 1e162976296fc77f93eb8a46bf2681c4a7694727317a384b38bdfc552cd7dba6738a84e035879df673256624a6bdf3c0a74e16a677ce06adae80b2ee9daf59b8
|
7
|
+
data.tar.gz: 5454c4de4150ceaa24c797ae726913ed0b239fdfcbf19471985d760d76c41a2b78da3ea1df4c733afb1ba75301e02ae0550252876e3b3e4b145e4984b4c5e3b4
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
3
|
rvm:
|
4
|
-
-
|
5
|
-
|
4
|
+
- jruby-19mode
|
5
|
+
- 2.1.2
|
6
6
|
- 1.9.3
|
7
|
+
# TODO - rbx-2
|
7
8
|
|
8
9
|
gemfile:
|
9
|
-
-
|
10
|
-
-
|
11
|
-
-
|
12
|
-
|
13
|
-
# - ci/Gemfile.rails-3.0.x
|
10
|
+
- gemfiles/activerecord_3.2.gemfile
|
11
|
+
- gemfiles/activerecord_4.0.gemfile
|
12
|
+
- gemfiles/activerecord_4.1.gemfile
|
13
|
+
- gemfiles/activerecord_edge.gemfile
|
14
14
|
|
15
15
|
env:
|
16
16
|
- DB=sqlite
|
@@ -23,41 +23,10 @@ before_script:
|
|
23
23
|
- mysql -e 'create database with_advisory_lock_test'
|
24
24
|
- psql -c 'create database with_advisory_lock_test' -U postgres
|
25
25
|
|
26
|
+
addons:
|
27
|
+
postgresql: "9.3"
|
28
|
+
|
26
29
|
matrix:
|
27
|
-
|
28
|
-
-
|
29
|
-
|
30
|
-
env: DB=sqlite
|
31
|
-
- rvm: 1.8.7
|
32
|
-
gemfile: ci/Gemfile.rails-4.0.x
|
33
|
-
env: DB=mysql
|
34
|
-
- rvm: 1.8.7
|
35
|
-
gemfile: ci/Gemfile.rails-4.0.x
|
36
|
-
env: DB=postgresql
|
37
|
-
- rvm: 2.0.0
|
38
|
-
gemfile: ci/Gemfile.rails-3.0.x
|
39
|
-
env: DB=sqlite
|
40
|
-
- rvm: 2.0.0
|
41
|
-
gemfile: ci/Gemfile.rails-3.0.x
|
42
|
-
env: DB=mysql
|
43
|
-
- rvm: 2.0.0
|
44
|
-
gemfile: ci/Gemfile.rails-3.0.x
|
45
|
-
env: DB=postgresql
|
46
|
-
- rvm: 2.0.0
|
47
|
-
gemfile: ci/Gemfile.rails-3.1.x
|
48
|
-
env: DB=sqlite
|
49
|
-
- rvm: 2.0.0
|
50
|
-
gemfile: ci/Gemfile.rails-3.1.x
|
51
|
-
env: DB=mysql
|
52
|
-
- rvm: 2.0.0
|
53
|
-
gemfile: ci/Gemfile.rails-3.1.x
|
54
|
-
env: DB=postgresql
|
55
|
-
- rvm: 2.0.0
|
56
|
-
gemfile: ci/Gemfile.rails-3.2.x
|
57
|
-
env: DB=sqlite
|
58
|
-
- rvm: 2.0.0
|
59
|
-
gemfile: ci/Gemfile.rails-3.2.x
|
60
|
-
env: DB=mysql
|
61
|
-
- rvm: 2.0.0
|
62
|
-
gemfile: ci/Gemfile.rails-3.2.x
|
63
|
-
env: DB=postgresql
|
30
|
+
allow_failures:
|
31
|
+
- gemfile: gemfiles/activerecord_edge.gemfile
|
32
|
+
- rvm: rbx-2
|
data/Appraisals
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
appraise "activerecord-3.2" do
|
2
|
+
gem 'activerecord', '~> 3.2.0'
|
3
|
+
end
|
4
|
+
|
5
|
+
appraise "activerecord-4.0" do
|
6
|
+
gem "activerecord", "~> 4.0.0"
|
7
|
+
end
|
8
|
+
|
9
|
+
appraise "activerecord-4.1" do
|
10
|
+
gem "activerecord", "~> 4.1.0"
|
11
|
+
end
|
12
|
+
|
13
|
+
appraise "activerecord-edge" do
|
14
|
+
gem "activerecord", github: "rails/rails"
|
15
|
+
gem 'arel', github: 'rails/arel'
|
16
|
+
end
|
data/Gemfile
CHANGED
@@ -1,3 +1,15 @@
|
|
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,15 +1,14 @@
|
|
1
1
|
# with_advisory_lock
|
2
2
|
|
3
|
-
Adds advisory locking (mutexes) to ActiveRecord 3.
|
3
|
+
Adds advisory locking (mutexes) to ActiveRecord 3.2, 4.0 and 4.1 when used with
|
4
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.
|
5
|
+
or [PostgreSQL](http://www.postgresql.org/docs/9.3/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS).
|
6
6
|
SQLite resorts to file locking.
|
7
7
|
|
8
8
|
[](https://travis-ci.org/mceachen/with_advisory_lock)
|
9
9
|
[](http://rubygems.org/gems/with_advisory_lock)
|
10
10
|
[](https://codeclimate.com/github/mceachen/with_advisory_lock)
|
11
11
|
[](https://gemnasium.com/mceachen/with_advisory_lock)
|
12
|
-
[](https://bitdeli.com/free "Bitdeli Badge")
|
13
12
|
|
14
13
|
## What's an "Advisory Lock"?
|
15
14
|
|
@@ -38,8 +37,14 @@ end
|
|
38
37
|
The second parameter for ```with_advisory_lock``` is ```timeout_seconds```, and defaults to ```nil```,
|
39
38
|
which means wait indefinitely for the lock.
|
40
39
|
|
41
|
-
|
40
|
+
A value of zero will try the lock only once. If the lock is acquired, the block
|
41
|
+
will be yielded to. If the lock is currently being held, the block will not be called.
|
42
42
|
|
43
|
+
Note that if a non-nil value is provided for `timeout_seconds`, the block will not be invoked if
|
44
|
+
the lock cannot be acquired within that timeframe.
|
45
|
+
|
46
|
+
### Return values
|
47
|
+
|
43
48
|
The return value of ```with_advisory_lock``` will be the result of the yielded block,
|
44
49
|
if the lock was able to be acquired and the block yielded, or ```false```, if you provided
|
45
50
|
a timeout_seconds value and the lock was not able to be acquired in time.
|
@@ -129,6 +134,19 @@ end
|
|
129
134
|
|
130
135
|
## Changelog
|
131
136
|
|
137
|
+
### 2.0.0
|
138
|
+
|
139
|
+
* Lock timeouts of 0 now attempt the lock once, as per suggested by
|
140
|
+
[Jon Leighton](https://github.com/jonleighton) and implemented by
|
141
|
+
[Abdelkader Boudih](https://github.com/seuros). Thanks to both of you!
|
142
|
+
* [Pull request 11](https://github.com/mceachen/with_advisory_lock/pull/11)
|
143
|
+
fixed a downstream issue with jruby support! Thanks, [Aaron Todd](https://github.com/ozzyaaron)!
|
144
|
+
* Added Travis tests for jruby
|
145
|
+
* Dropped support for Rails 3.0, 3.1, and Ruby 1.8.7, as they are no longer
|
146
|
+
receiving security patches. See http://rubyonrails.org/security/ for more information.
|
147
|
+
This required the major version bump.
|
148
|
+
* Refactored `advisory_lock_exists?` to use existing functionality
|
149
|
+
* Fixed sqlite's implementation so parallel tests could be run against it
|
132
150
|
|
133
151
|
### 1.0.0
|
134
152
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 3.2.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 => "../"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.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 => "../"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.1.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 => "../"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", :github => "rails/rails"
|
6
|
+
gem "arel", :github => "rails/arel"
|
7
|
+
|
8
|
+
platforms :ruby do
|
9
|
+
gem "mysql2"
|
10
|
+
gem "pg"
|
11
|
+
gem "sqlite3"
|
12
|
+
end
|
13
|
+
|
14
|
+
platforms :jruby do
|
15
|
+
gem "activerecord-jdbcmysql-adapter"
|
16
|
+
gem "activerecord-jdbcpostgresql-adapter"
|
17
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
18
|
+
end
|
19
|
+
|
20
|
+
gemspec :path => "../"
|
data/lib/with_advisory_lock.rb
CHANGED
@@ -1,4 +1,17 @@
|
|
1
|
+
require 'with_advisory_lock/version'
|
2
|
+
|
3
|
+
module WithAdvisoryLock
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
autoload :Concern
|
7
|
+
autoload :Base
|
8
|
+
autoload :DatabaseAdapterSupport
|
9
|
+
autoload :Flock
|
10
|
+
autoload :MySQL, 'with_advisory_lock/mysql'
|
11
|
+
autoload :NestedAdvisoryLockError
|
12
|
+
autoload :PostgreSQL, 'with_advisory_lock/postgresql'
|
13
|
+
end
|
14
|
+
|
1
15
|
ActiveSupport.on_load :active_record do
|
2
|
-
require 'with_advisory_lock/concern'
|
3
16
|
ActiveRecord::Base.send :include, WithAdvisoryLock::Concern
|
4
17
|
end
|
@@ -7,35 +7,21 @@ module WithAdvisoryLock
|
|
7
7
|
def initialize(connection, lock_name, timeout_seconds)
|
8
8
|
@connection = connection
|
9
9
|
@lock_name = lock_name
|
10
|
-
lock_name_prefix = ENV['WITH_ADVISORY_LOCK_PREFIX']
|
11
|
-
if lock_name_prefix
|
12
|
-
@lock_name = if lock_name.is_a? Numeric
|
13
|
-
"#{lock_name_prefix.to_i}#{lock_name}".to_i
|
14
|
-
else
|
15
|
-
"#{lock_name_prefix}#{lock_name}"
|
16
|
-
end
|
17
|
-
end
|
18
10
|
@timeout_seconds = timeout_seconds
|
19
11
|
end
|
20
12
|
|
21
|
-
def
|
22
|
-
|
13
|
+
def lock_str
|
14
|
+
@lock_str ||= "#{ENV['WITH_ADVISORY_LOCK_PREFIX'].to_s}#{lock_name.to_s}"
|
23
15
|
end
|
24
16
|
|
25
17
|
def self.lock_stack
|
26
18
|
Thread.current[:with_advisory_lock_stack] ||= []
|
27
19
|
end
|
28
20
|
|
29
|
-
|
30
|
-
self.class.lock_stack
|
31
|
-
end
|
21
|
+
delegate :lock_stack, to: 'self.class'
|
32
22
|
|
33
23
|
def already_locked?
|
34
|
-
lock_stack.include?
|
35
|
-
end
|
36
|
-
|
37
|
-
def advisory_lock_exists?(name)
|
38
|
-
raise NoMethodError, "method must be implemented in implementation subclasses"
|
24
|
+
lock_stack.include? lock_str
|
39
25
|
end
|
40
26
|
|
41
27
|
def with_advisory_lock_if_needed
|
@@ -56,12 +42,18 @@ module WithAdvisoryLock
|
|
56
42
|
end
|
57
43
|
end
|
58
44
|
|
45
|
+
def advisory_lock_exists?
|
46
|
+
acquired_lock = try_lock
|
47
|
+
ensure
|
48
|
+
release_lock if acquired_lock
|
49
|
+
end
|
50
|
+
|
59
51
|
def yield_with_lock
|
60
52
|
give_up_at = Time.now + @timeout_seconds if @timeout_seconds
|
61
|
-
|
53
|
+
begin
|
62
54
|
if try_lock
|
63
55
|
begin
|
64
|
-
lock_stack.push(
|
56
|
+
lock_stack.push(lock_str)
|
65
57
|
return yield
|
66
58
|
ensure
|
67
59
|
lock_stack.pop
|
@@ -72,8 +64,13 @@ module WithAdvisoryLock
|
|
72
64
|
# Randomizing sleep time may help reduce contention.
|
73
65
|
sleep(rand * 0.15 + 0.05)
|
74
66
|
end
|
75
|
-
end
|
67
|
+
end while @timeout_seconds.nil? || Time.now < give_up_at
|
76
68
|
false # failed to get lock in time.
|
77
69
|
end
|
70
|
+
|
71
|
+
# The timestamp prevents AR from caching the result improperly, and is ignored.
|
72
|
+
def query_cache_buster
|
73
|
+
"AS t#{(Time.now.to_f * 1000).to_i}"
|
74
|
+
end
|
78
75
|
end
|
79
76
|
end
|
@@ -2,23 +2,12 @@
|
|
2
2
|
# but rails autoloading is too clever by half. Pull requests are welcome.
|
3
3
|
|
4
4
|
require 'active_support/concern'
|
5
|
-
require 'with_advisory_lock/base'
|
6
|
-
require 'with_advisory_lock/database_adapter_support'
|
7
|
-
require 'with_advisory_lock/flock'
|
8
|
-
require 'with_advisory_lock/mysql'
|
9
|
-
require 'with_advisory_lock/postgresql'
|
10
5
|
|
11
6
|
module WithAdvisoryLock
|
12
7
|
module Concern
|
13
8
|
extend ActiveSupport::Concern
|
14
9
|
|
15
|
-
|
16
|
-
self.class.with_advisory_lock(lock_name, timeout_seconds, &block)
|
17
|
-
end
|
18
|
-
|
19
|
-
def advisory_lock_exists?(lock_name)
|
20
|
-
self.class.advisory_lock_exists?(lock_name)
|
21
|
-
end
|
10
|
+
delegate :with_advisory_lock, :advisory_lock_exists?, to: 'self.class'
|
22
11
|
|
23
12
|
module ClassMethods
|
24
13
|
def with_advisory_lock(lock_name, timeout_seconds=nil, &block)
|
@@ -27,23 +16,23 @@ module WithAdvisoryLock
|
|
27
16
|
end
|
28
17
|
|
29
18
|
def advisory_lock_exists?(lock_name)
|
30
|
-
impl = impl_class.new(connection, lock_name,
|
31
|
-
impl.
|
19
|
+
impl = impl_class.new(connection, lock_name, 0)
|
20
|
+
impl.already_locked? || !impl.yield_with_lock { true }
|
32
21
|
end
|
33
22
|
|
34
23
|
def current_advisory_lock
|
35
24
|
WithAdvisoryLock::Base.lock_stack.first
|
36
25
|
end
|
37
26
|
|
38
|
-
|
27
|
+
private
|
39
28
|
|
40
29
|
def impl_class
|
41
|
-
|
42
|
-
|
30
|
+
case WithAdvisoryLock::DatabaseAdapterSupport.new(connection).adapter
|
31
|
+
when :postgresql
|
43
32
|
WithAdvisoryLock::PostgreSQL
|
44
|
-
|
33
|
+
when :mysql
|
45
34
|
WithAdvisoryLock::MySQL
|
46
|
-
else
|
35
|
+
else #sqlite
|
47
36
|
WithAdvisoryLock::Flock
|
48
37
|
end
|
49
38
|
end
|
@@ -5,8 +5,8 @@ module WithAdvisoryLock
|
|
5
5
|
|
6
6
|
def filename
|
7
7
|
@filename ||= begin
|
8
|
-
safe =
|
9
|
-
fn = ".lock-#{safe}-#{stable_hashcode(
|
8
|
+
safe = lock_str.to_s.gsub(/[^a-z0-9]/i, '')
|
9
|
+
fn = ".lock-#{safe}-#{stable_hashcode(lock_str)}"
|
10
10
|
# Let the user specify a directory besides CWD.
|
11
11
|
ENV['FLOCK_DIR'] ? File.expand_path(fn, ENV['FLOCK_DIR']) : fn
|
12
12
|
end
|
@@ -1,9 +1,6 @@
|
|
1
|
-
require 'with_advisory_lock/nested_advisory_lock_error'
|
2
1
|
module WithAdvisoryLock
|
3
2
|
class MySQL < Base
|
4
|
-
|
5
3
|
# See http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock
|
6
|
-
|
7
4
|
def try_lock
|
8
5
|
unless lock_stack.empty?
|
9
6
|
raise NestedAdvisoryLockError.new(
|
@@ -14,30 +11,27 @@ module WithAdvisoryLock
|
|
14
11
|
# 0 if the attempt timed out (for example, because another client has
|
15
12
|
# previously locked the name), or NULL if an error occurred
|
16
13
|
# (such as running out of memory or the thread was killed with mysqladmin kill).
|
17
|
-
|
18
|
-
sql
|
19
|
-
1 == connection.select_value(sql).to_i
|
14
|
+
sql = "SELECT GET_LOCK(#{quoted_lock_str}, 0) #{query_cache_buster}"
|
15
|
+
connection.select_value(sql).to_i > 0
|
20
16
|
end
|
21
17
|
|
22
18
|
def release_lock
|
23
19
|
# Returns > 0 if the lock was released,
|
24
|
-
# 0 if the lock was not established by this thread
|
25
|
-
# in which case the lock is not released), and
|
20
|
+
# 0 if the lock was not established by this thread
|
21
|
+
# (in which case the lock is not released), and
|
26
22
|
# NULL if the named lock did not exist.
|
27
|
-
|
28
|
-
sql
|
29
|
-
1 == connection.select_value(sql).to_i
|
23
|
+
sql = "SELECT RELEASE_LOCK(#{quoted_lock_str}) #{query_cache_buster}"
|
24
|
+
connection.select_value(sql).to_i > 0
|
30
25
|
end
|
31
26
|
|
27
|
+
# MySQL doesn't support nested locks:
|
32
28
|
def already_locked?
|
33
|
-
lock_stack.last ==
|
29
|
+
lock_stack.last == lock_str
|
34
30
|
end
|
35
31
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
sql = "SELECT IS_USED_LOCK(#{quoted_name})"
|
40
|
-
connection.select_value(sql).present?
|
32
|
+
# MySQL wants a string as the lock key.
|
33
|
+
def quoted_lock_str
|
34
|
+
connection.quote(lock_str)
|
41
35
|
end
|
42
36
|
end
|
43
37
|
end
|
@@ -1,29 +1,27 @@
|
|
1
|
-
|
2
1
|
module WithAdvisoryLock
|
3
2
|
class PostgreSQL < Base
|
4
|
-
|
5
3
|
# See http://www.postgresql.org/docs/9.1/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
|
6
|
-
|
7
4
|
def try_lock
|
8
|
-
# pg_try_advisory_lock will either obtain the lock immediately
|
9
|
-
#
|
10
|
-
sql = "SELECT pg_try_advisory_lock(#{
|
11
|
-
|
5
|
+
# pg_try_advisory_lock will either obtain the lock immediately and return true
|
6
|
+
# or return false if the lock cannot be acquired immediately
|
7
|
+
sql = "SELECT pg_try_advisory_lock(#{lock_keys.join(',')}) #{query_cache_buster}"
|
8
|
+
't' == connection.select_value(sql).to_s
|
12
9
|
end
|
13
10
|
|
14
11
|
def release_lock
|
15
|
-
sql = "SELECT pg_advisory_unlock(#{
|
16
|
-
|
12
|
+
sql = "SELECT pg_advisory_unlock(#{lock_keys.join(',')}) #{query_cache_buster}"
|
13
|
+
't' == connection.select_value(sql).to_s
|
17
14
|
end
|
18
15
|
|
19
|
-
|
20
|
-
|
16
|
+
# PostgreSQL wants 2 32bit integers as the lock key.
|
17
|
+
def lock_keys
|
18
|
+
@lock_keys ||= begin
|
19
|
+
[stable_hashcode(lock_name), ENV['WITH_ADVISORY_LOCK_PREFIX']].map do |ea|
|
20
|
+
# pg advisory args must be 31 bit ints
|
21
|
+
ea.to_i & 0x7fffffff
|
22
|
+
end
|
23
|
+
end
|
21
24
|
end
|
22
|
-
|
23
|
-
def advisory_lock_exists?(name)
|
24
|
-
sql = "SELECT 't'::text FROM pg_locks WHERE objid = #{numeric_lock(name)} AND locktype = 'advisory'"
|
25
|
-
"t" == connection.select_value(sql).to_s
|
26
|
-
end
|
27
|
-
|
28
25
|
end
|
29
26
|
end
|
27
|
+
|
data/test/lock_test.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
require 'minitest_helper'
|
2
2
|
|
3
3
|
describe 'class methods' do
|
4
|
-
|
5
4
|
let(:lock_name) { "test lock #{rand(1024)}" }
|
6
|
-
let(:expected_lock_name) { "#{ENV['WITH_ADVISORY_LOCK_PREFIX']}#{lock_name}" }
|
7
5
|
|
8
6
|
describe '.current_advisory_lock' do
|
9
7
|
it "returns nil outside an advisory lock request" do
|
@@ -12,21 +10,30 @@ describe 'class methods' do
|
|
12
10
|
|
13
11
|
it 'returns the name of the last lock acquired' do
|
14
12
|
Tag.with_advisory_lock(lock_name) do
|
15
|
-
Tag.current_advisory_lock.
|
13
|
+
Tag.current_advisory_lock.must_match /#{lock_name}/
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
20
18
|
describe '.advisory_lock_exists?' do
|
21
19
|
it "returns false for an unacquired lock" do
|
22
|
-
Tag.advisory_lock_exists?(
|
20
|
+
Tag.advisory_lock_exists?(lock_name).must_be_false
|
23
21
|
end
|
24
22
|
|
25
23
|
it 'returns the name of the last lock acquired' do
|
26
24
|
Tag.with_advisory_lock(lock_name) do
|
27
|
-
Tag.advisory_lock_exists?(
|
25
|
+
Tag.advisory_lock_exists?(lock_name).must_be_true
|
28
26
|
end
|
29
27
|
end
|
30
28
|
end
|
31
29
|
|
32
|
-
|
30
|
+
describe "0 timeout" do
|
31
|
+
it 'attempts the lock exactly once with no timeout' do
|
32
|
+
block_was_yielded = false
|
33
|
+
Tag.with_advisory_lock(lock_name, 0) do
|
34
|
+
block_was_yielded = true
|
35
|
+
end
|
36
|
+
block_was_yielded.must_be_true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/test/minitest_helper.rb
CHANGED
@@ -2,28 +2,31 @@ require 'erb'
|
|
2
2
|
require 'active_record'
|
3
3
|
require 'with_advisory_lock'
|
4
4
|
require 'tmpdir'
|
5
|
+
require 'securerandom'
|
5
6
|
|
6
7
|
db_config = File.expand_path("database.yml", File.dirname(__FILE__))
|
7
8
|
ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(db_config)).result)
|
8
9
|
|
9
10
|
def env_db
|
10
|
-
ENV["DB"] ||
|
11
|
+
(ENV["DB"] || :mysql).to_sym
|
11
12
|
end
|
12
13
|
|
14
|
+
ENV["WITH_ADVISORY_LOCK_PREFIX"] ||= SecureRandom.base64
|
15
|
+
|
13
16
|
ActiveRecord::Base.establish_connection(env_db)
|
14
17
|
ActiveRecord::Migration.verbose = false
|
15
18
|
|
16
19
|
require 'test_models'
|
20
|
+
begin
|
21
|
+
require 'minitest'
|
22
|
+
rescue LoadError => rails_four_zero_is_lame
|
23
|
+
end
|
17
24
|
require 'minitest/autorun'
|
18
25
|
require 'minitest/great_expectations'
|
19
26
|
require 'mocha/setup'
|
20
27
|
|
21
28
|
Thread.abort_on_exception = true
|
22
29
|
|
23
|
-
def test_lock_exists?
|
24
|
-
%w{mysql postgres}.include? env_db
|
25
|
-
end
|
26
|
-
|
27
30
|
class MiniTest::Spec
|
28
31
|
before do
|
29
32
|
ENV['FLOCK_DIR'] = Dir.mktmpdir
|
data/test/nesting_test.rb
CHANGED
@@ -26,7 +26,7 @@ describe "lock nesting" do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
it "raises errors with MySQL when acquiring nested lock" do
|
29
|
-
skip unless env_db ==
|
29
|
+
skip unless env_db == :mysql
|
30
30
|
exc = proc {
|
31
31
|
Tag.with_advisory_lock("first") do
|
32
32
|
Tag.with_advisory_lock("second") do
|
@@ -37,7 +37,7 @@ describe "lock nesting" do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it "supports nested advisory locks with !MySQL" do
|
40
|
-
skip if env_db ==
|
40
|
+
skip if env_db == :mysql
|
41
41
|
impl = WithAdvisoryLock::Base.new(nil, nil, nil)
|
42
42
|
impl.lock_stack.must_be_empty
|
43
43
|
Tag.with_advisory_lock("first") do
|
data/test/parallelism_test.rb
CHANGED
@@ -1,97 +1,115 @@
|
|
1
1
|
require 'minitest_helper'
|
2
2
|
|
3
|
-
parallelism_is_broken = begin
|
4
|
-
# Rails < 3.2 has known bugs with parallelism
|
5
|
-
(ActiveRecord::VERSION::MAJOR <= 3 && ActiveRecord::VERSION::MINOR < 2) ||
|
6
|
-
# SQLite doesn't support parallel writes
|
7
|
-
ENV["DB"] =~ /sqlite/
|
8
|
-
end
|
9
|
-
|
10
3
|
describe "parallelism" do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
4
|
+
class FindOrCreateWorker
|
5
|
+
def initialize(target, run_at, name, use_advisory_lock)
|
6
|
+
@thread = Thread.new do
|
7
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
8
|
+
sleep((run_at - Time.now).to_f)
|
9
|
+
if use_advisory_lock
|
10
|
+
Tag.with_advisory_lock(name) { work(name) }
|
11
|
+
else
|
12
|
+
work(name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def work(name)
|
16
19
|
Tag.transaction do
|
17
|
-
Tag.
|
20
|
+
Tag.where(name: name).first_or_create
|
18
21
|
end
|
19
22
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
task.call
|
23
|
+
|
24
|
+
def join
|
25
|
+
@thread.join
|
24
26
|
end
|
25
|
-
ActiveRecord::Base.connection.close if ActiveRecord::Base.connection.respond_to?(:close)
|
26
27
|
end
|
27
28
|
|
28
|
-
def run_workers
|
29
|
-
|
30
|
-
@iterations.times
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
29
|
+
def run_workers
|
30
|
+
all_workers = []
|
31
|
+
@names = @iterations.times.map { |iter| "iteration ##{iter}" }
|
32
|
+
@names.each do |name|
|
33
|
+
wake_time = 1.second.from_now
|
34
|
+
workers = @workers.times.map do
|
35
|
+
FindOrCreateWorker.new(@target, wake_time, name, @use_advisory_lock)
|
36
36
|
end
|
37
|
-
|
37
|
+
workers.each(&:join)
|
38
|
+
all_workers += workers
|
38
39
|
end
|
39
|
-
|
40
|
+
# Ensure we're still connected:
|
41
|
+
ActiveRecord::Base.connection_pool.connection
|
42
|
+
all_workers
|
40
43
|
end
|
41
44
|
|
42
45
|
before :each do
|
43
|
-
|
44
|
-
@workers =
|
46
|
+
ActiveRecord::Base.connection.reconnect!
|
47
|
+
@workers = 10
|
45
48
|
end
|
46
49
|
|
47
|
-
it "
|
48
|
-
|
50
|
+
it "creates multiple duplicate rows without advisory locks" do
|
51
|
+
@use_advisory_lock = false
|
52
|
+
@iterations = 1
|
53
|
+
run_workers
|
49
54
|
Tag.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
|
50
55
|
TagAudit.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
|
51
56
|
Label.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
|
52
|
-
end
|
57
|
+
end unless env_db == :sqlite
|
53
58
|
|
54
|
-
it "
|
55
|
-
|
59
|
+
it "doesn't create multiple duplicate rows with advisory locks" do
|
60
|
+
@use_advisory_lock = true
|
61
|
+
@iterations = 10
|
62
|
+
run_workers
|
56
63
|
Tag.all.size.must_equal @iterations # <- any duplicated rows will NOT make me happy.
|
57
64
|
TagAudit.all.size.must_equal @iterations # <- any duplicated rows will NOT make me happy.
|
58
65
|
Label.all.size.must_equal @iterations # <- any duplicated rows will NOT make me happy.
|
59
66
|
end
|
67
|
+
end
|
60
68
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
describe "separate thread tests" do
|
70
|
+
let(:lock_name) { "testing 1,2,3" }
|
71
|
+
|
72
|
+
before do
|
73
|
+
@t1_acquired_lock = false
|
74
|
+
@t1_return_value = nil
|
75
|
+
|
76
|
+
@t1 = Thread.new do
|
77
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
78
|
+
@t1_return_value = Label.with_advisory_lock(lock_name) do
|
79
|
+
t1_acquired_lock = true
|
80
|
+
sleep(0.4)
|
81
|
+
't1 finished'
|
82
|
+
end
|
70
83
|
end
|
71
84
|
end
|
72
85
|
|
73
|
-
#
|
86
|
+
# Wait for the thread to acquire the lock:
|
74
87
|
sleep(0.1)
|
88
|
+
ActiveRecord::Base.connection.reconnect!
|
89
|
+
end
|
75
90
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
85
|
-
end
|
91
|
+
after do
|
92
|
+
@t1.join
|
93
|
+
end
|
94
|
+
|
95
|
+
it "#with_advisory_lock with a 0 timeout returns false immediately" do
|
96
|
+
response = Label.with_advisory_lock(lock_name, 0) {}
|
97
|
+
response.must_be_false
|
98
|
+
end
|
86
99
|
|
87
|
-
|
88
|
-
|
89
|
-
|
100
|
+
it "#advisory_lock_exists? returns true when another thread has the lock" do
|
101
|
+
Tag.advisory_lock_exists?(lock_name).must_be_true
|
102
|
+
end
|
90
103
|
|
91
|
-
|
92
|
-
|
104
|
+
it "can re-establish the lock after the other thread releases it" do
|
105
|
+
@t1.join
|
106
|
+
@t1_return_value.must_equal 't1 finished'
|
93
107
|
|
94
|
-
|
95
|
-
|
108
|
+
# We should now be able to acquire the lock immediately:
|
109
|
+
reacquired = false
|
110
|
+
Label.with_advisory_lock(lock_name, 0) do
|
111
|
+
reacquired = true
|
112
|
+
end.must_be_true
|
113
|
+
reacquired.must_be_true
|
96
114
|
end
|
97
|
-
end
|
115
|
+
end
|
data/test/test_models.rb
CHANGED
data/tests.sh
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
#!/bin/
|
2
|
-
export
|
1
|
+
#!/bin/bash -e
|
2
|
+
export DB
|
3
3
|
|
4
|
-
for
|
5
|
-
|
6
|
-
do
|
7
|
-
echo $DB $
|
8
|
-
bundle
|
4
|
+
for RUBY in 2.1.2 jruby-1.7.12 ; do
|
5
|
+
rbenv local $RUBY
|
6
|
+
for DB in mysql postgresql sqlite ; do
|
7
|
+
echo "$DB | $(ruby -v)"
|
8
|
+
appraisal bundle update
|
9
|
+
appraisal rake test
|
9
10
|
done
|
10
11
|
done
|
data/with_advisory_lock.gemspec
CHANGED
@@ -18,14 +18,12 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^test/})
|
19
19
|
gem.require_paths = %w(lib)
|
20
20
|
|
21
|
-
gem.add_runtime_dependency 'activerecord', '>= 3.
|
21
|
+
gem.add_runtime_dependency 'activerecord', '>= 3.2'
|
22
22
|
|
23
23
|
gem.add_development_dependency 'rake'
|
24
24
|
gem.add_development_dependency 'yard'
|
25
25
|
gem.add_development_dependency 'minitest'
|
26
26
|
gem.add_development_dependency 'minitest-great_expectations'
|
27
27
|
gem.add_development_dependency 'mocha'
|
28
|
-
gem.add_development_dependency '
|
29
|
-
gem.add_development_dependency 'pg'
|
30
|
-
gem.add_development_dependency 'sqlite3'
|
28
|
+
gem.add_development_dependency 'appraisal'
|
31
29
|
end
|
metadata
CHANGED
@@ -1,139 +1,111 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: with_advisory_lock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.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: 2014-
|
11
|
+
date: 2014-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 3.
|
19
|
+
version: '3.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 3.
|
26
|
+
version: '3.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: yard
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: minitest-great_expectations
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: mocha
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: appraisal
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: pg
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ! '>='
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ! '>='
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: sqlite3
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ! '>='
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '0'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ! '>='
|
108
|
+
- - ">="
|
137
109
|
- !ruby/object:Gem::Version
|
138
110
|
version: '0'
|
139
111
|
description: Advisory locking for ActiveRecord
|
@@ -143,17 +115,17 @@ executables: []
|
|
143
115
|
extensions: []
|
144
116
|
extra_rdoc_files: []
|
145
117
|
files:
|
146
|
-
- .gitignore
|
147
|
-
- .travis.yml
|
118
|
+
- ".gitignore"
|
119
|
+
- ".travis.yml"
|
120
|
+
- Appraisals
|
148
121
|
- Gemfile
|
149
122
|
- LICENSE.txt
|
150
123
|
- README.md
|
151
124
|
- Rakefile
|
152
|
-
-
|
153
|
-
-
|
154
|
-
-
|
155
|
-
-
|
156
|
-
- ci/Gemfile.rails-4.1.x
|
125
|
+
- gemfiles/activerecord_3.2.gemfile
|
126
|
+
- gemfiles/activerecord_4.0.gemfile
|
127
|
+
- gemfiles/activerecord_4.1.gemfile
|
128
|
+
- gemfiles/activerecord_edge.gemfile
|
157
129
|
- lib/with_advisory_lock.rb
|
158
130
|
- lib/with_advisory_lock/base.rb
|
159
131
|
- lib/with_advisory_lock/concern.rb
|
@@ -169,7 +141,6 @@ files:
|
|
169
141
|
- test/minitest_helper.rb
|
170
142
|
- test/nesting_test.rb
|
171
143
|
- test/parallelism_test.rb
|
172
|
-
- test/simple_parallel_test.rb
|
173
144
|
- test/test_models.rb
|
174
145
|
- tests.sh
|
175
146
|
- with_advisory_lock.gemspec
|
@@ -183,17 +154,17 @@ require_paths:
|
|
183
154
|
- lib
|
184
155
|
required_ruby_version: !ruby/object:Gem::Requirement
|
185
156
|
requirements:
|
186
|
-
- -
|
157
|
+
- - ">="
|
187
158
|
- !ruby/object:Gem::Version
|
188
159
|
version: '0'
|
189
160
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
161
|
requirements:
|
191
|
-
- -
|
162
|
+
- - ">="
|
192
163
|
- !ruby/object:Gem::Version
|
193
164
|
version: '0'
|
194
165
|
requirements: []
|
195
166
|
rubyforge_project:
|
196
|
-
rubygems_version: 2.
|
167
|
+
rubygems_version: 2.3.0
|
197
168
|
signing_key:
|
198
169
|
specification_version: 4
|
199
170
|
summary: Advisory locking for ActiveRecord
|
@@ -204,6 +175,5 @@ test_files:
|
|
204
175
|
- test/minitest_helper.rb
|
205
176
|
- test/nesting_test.rb
|
206
177
|
- test/parallelism_test.rb
|
207
|
-
- test/simple_parallel_test.rb
|
208
178
|
- test/test_models.rb
|
209
179
|
has_rdoc:
|
data/ci/Gemfile.rails-3.0.x
DELETED
data/ci/Gemfile.rails-3.1.x
DELETED
data/ci/Gemfile.rails-3.2.x
DELETED
data/ci/Gemfile.rails-4.0.x
DELETED
data/ci/Gemfile.rails-4.1.x
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
require 'minitest_helper'
|
2
|
-
|
3
|
-
describe "prevents threads from accessing a resource concurrently" do
|
4
|
-
def assert_correct_parallel_behavior(lock_name)
|
5
|
-
times = ActiveSupport::OrderedHash.new
|
6
|
-
ActiveRecord::Base.connection_pool.disconnect!
|
7
|
-
t1 = Thread.new do
|
8
|
-
ActiveRecord::Base.connection.reconnect!
|
9
|
-
ActiveRecord::Base.with_advisory_lock(lock_name) do
|
10
|
-
times[:t1_acquire] = Time.now
|
11
|
-
sleep 0.5
|
12
|
-
end
|
13
|
-
times[:t1_release] = Time.now
|
14
|
-
end
|
15
|
-
sleep 0.1
|
16
|
-
t2 = Thread.new do
|
17
|
-
ActiveRecord::Base.connection.reconnect!
|
18
|
-
ActiveRecord::Base.with_advisory_lock(lock_name) do
|
19
|
-
times[:t2_acquire] = Time.now
|
20
|
-
sleep 1
|
21
|
-
end
|
22
|
-
times[:t2_release] = Time.now
|
23
|
-
end
|
24
|
-
t1.join
|
25
|
-
t2.join
|
26
|
-
times.keys.must_equal [:t1_acquire, :t1_release, :t2_acquire, :t2_release]
|
27
|
-
times[:t2_acquire].must_be :>, times[:t1_release]
|
28
|
-
end
|
29
|
-
|
30
|
-
it "with a string lock name" do
|
31
|
-
assert_correct_parallel_behavior("example lock name")
|
32
|
-
end
|
33
|
-
|
34
|
-
it "with a numeric lock name" do
|
35
|
-
assert_correct_parallel_behavior(1234)
|
36
|
-
end
|
37
|
-
end
|