with_advisory_lock 2.0.0 → 3.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 +4 -4
- data/.travis.yml +2 -3
- data/README.md +20 -1
- data/lib/with_advisory_lock/base.rb +44 -27
- data/lib/with_advisory_lock/concern.rb +10 -9
- data/lib/with_advisory_lock/database_adapter_support.rb +0 -6
- data/lib/with_advisory_lock/mysql.rb +6 -11
- data/lib/with_advisory_lock/postgresql.rb +9 -6
- data/lib/with_advisory_lock/version.rb +1 -1
- data/test/lock_test.rb +8 -8
- data/test/minitest_helper.rb +12 -8
- data/test/parallelism_test.rb +30 -70
- data/test/thread_test.rb +60 -0
- data/tests.sh +3 -3
- data/with_advisory_lock.gemspec +2 -1
- metadata +55 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aeb99a0ddec77a51eeb59a41a26411a25390c65b
|
4
|
+
data.tar.gz: 3ee6f67835cbd6f3fe515f41b8134478ef2102ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58919a4b30f793b056f8c9f2af7f06f40673f5e9e6ef68213760f49b348f9044087ab16a1b398619e7c47bcba14db7eecadaf50a6f5652bfa5e36bb0ef1d9fc7
|
7
|
+
data.tar.gz: a819c9b4d0c2607505b90f9dbb8f981c041c3608ae69a63e7dac621d105479be268b5d425f1ec946c225b36175d595ed2115782a9a239cd3ad671c6e3b25d665
|
data/.travis.yml
CHANGED
@@ -23,10 +23,9 @@ 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
|
-
|
29
26
|
matrix:
|
30
27
|
allow_failures:
|
31
28
|
- gemfile: gemfiles/activerecord_edge.gemfile
|
29
|
+
- rvm: jruby-19mode # travis' version of jruby has issues. Tests pass with jruby 1.7.13/java 1.8.0_11 on mac.
|
32
30
|
- rvm: rbx-2
|
31
|
+
|
data/README.md
CHANGED
@@ -41,9 +41,14 @@ A value of zero will try the lock only once. If the lock is acquired, the block
|
|
41
41
|
will be yielded to. If the lock is currently being held, the block will not be called.
|
42
42
|
|
43
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
|
44
|
+
the lock cannot be acquired within that time-frame.
|
45
45
|
|
46
46
|
### Return values
|
47
|
+
|
48
|
+
The return value of `with_advisory_lock_result` is a `WithAdvisoryLock::Result` instance,
|
49
|
+
which has a `lock_was_acquired?` method and a `result` accessor method, which is
|
50
|
+
the returned value of the given block. If your block may validly return false, you should use
|
51
|
+
this method.
|
47
52
|
|
48
53
|
The return value of ```with_advisory_lock``` will be the result of the yielded block,
|
49
54
|
if the lock was able to be acquired and the block yielded, or ```false```, if you provided
|
@@ -134,6 +139,20 @@ end
|
|
134
139
|
|
135
140
|
## Changelog
|
136
141
|
|
142
|
+
### 3.0.0
|
143
|
+
|
144
|
+
* Added jruby/PostgreSQL support for Rails 4.x
|
145
|
+
* Reworked threaded tests to allow jruby tests to pass
|
146
|
+
|
147
|
+
#### API changes
|
148
|
+
|
149
|
+
* `yield_with_lock_and_timeout` and `yield_with_lock` now return instances of
|
150
|
+
`WithAdvisoryLock::Result`, so blocks that return `false` are not misinterpreted
|
151
|
+
as a failure to lock. As this changes the interface (albeit internal methods), the major version
|
152
|
+
number was incremented.
|
153
|
+
* `with_advisory_lock_result` was introduced, which clarifies whether the lock was acquired
|
154
|
+
versus the yielded block returned false.
|
155
|
+
|
137
156
|
### 2.0.0
|
138
157
|
|
139
158
|
* Lock timeouts of 0 now attempt the lock once, as per suggested by
|
@@ -1,6 +1,21 @@
|
|
1
1
|
require 'zlib'
|
2
2
|
|
3
3
|
module WithAdvisoryLock
|
4
|
+
class Result
|
5
|
+
attr_reader :result
|
6
|
+
|
7
|
+
def initialize(lock_was_acquired, result = false)
|
8
|
+
@lock_was_acquired = lock_was_acquired
|
9
|
+
@result = result
|
10
|
+
end
|
11
|
+
|
12
|
+
def lock_was_acquired?
|
13
|
+
@lock_was_acquired
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
FAILED_TO_LOCK = Result.new(false)
|
18
|
+
|
4
19
|
class Base
|
5
20
|
attr_reader :connection, :lock_name, :timeout_seconds
|
6
21
|
|
@@ -15,20 +30,22 @@ module WithAdvisoryLock
|
|
15
30
|
end
|
16
31
|
|
17
32
|
def self.lock_stack
|
33
|
+
# access doesn't need to be synchronized as it is only accessed by the current thread.
|
18
34
|
Thread.current[:with_advisory_lock_stack] ||= []
|
19
35
|
end
|
20
|
-
|
21
36
|
delegate :lock_stack, to: 'self.class'
|
22
37
|
|
23
38
|
def already_locked?
|
24
39
|
lock_stack.include? lock_str
|
25
40
|
end
|
26
41
|
|
27
|
-
def with_advisory_lock_if_needed
|
42
|
+
def with_advisory_lock_if_needed(&block)
|
28
43
|
if already_locked?
|
29
|
-
yield
|
44
|
+
Result.new(true, yield)
|
45
|
+
elsif timeout_seconds == 0
|
46
|
+
yield_with_lock(&block)
|
30
47
|
else
|
31
|
-
|
48
|
+
yield_with_lock_and_timeout(&block)
|
32
49
|
end
|
33
50
|
end
|
34
51
|
|
@@ -42,35 +59,35 @@ module WithAdvisoryLock
|
|
42
59
|
end
|
43
60
|
end
|
44
61
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
62
|
+
def yield_with_lock_and_timeout(&block)
|
63
|
+
give_up_at = Time.now + @timeout_seconds if @timeout_seconds
|
64
|
+
while @timeout_seconds.nil? || Time.now < give_up_at do
|
65
|
+
r = yield_with_lock(&block)
|
66
|
+
return r if r.lock_was_acquired?
|
67
|
+
# Randomizing sleep time may help reduce contention.
|
68
|
+
sleep(rand(0.05..0.15))
|
69
|
+
end
|
70
|
+
FAILED_TO_LOCK
|
49
71
|
end
|
50
72
|
|
51
73
|
def yield_with_lock
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
release_lock
|
61
|
-
end
|
62
|
-
else
|
63
|
-
# sleep between 1/20 and ~1/5 of a second.
|
64
|
-
# Randomizing sleep time may help reduce contention.
|
65
|
-
sleep(rand * 0.15 + 0.05)
|
74
|
+
if try_lock
|
75
|
+
begin
|
76
|
+
lock_stack.push(lock_str)
|
77
|
+
result = block_given? ? yield : nil
|
78
|
+
Result.new(true, result)
|
79
|
+
ensure
|
80
|
+
lock_stack.pop
|
81
|
+
release_lock
|
66
82
|
end
|
67
|
-
|
68
|
-
|
83
|
+
else
|
84
|
+
FAILED_TO_LOCK
|
85
|
+
end
|
69
86
|
end
|
70
87
|
|
71
|
-
#
|
72
|
-
def
|
73
|
-
"
|
88
|
+
# Prevent AR from caching results improperly
|
89
|
+
def unique_column_name
|
90
|
+
"t#{SecureRandom.hex}"
|
74
91
|
end
|
75
92
|
end
|
76
93
|
end
|
@@ -1,23 +1,24 @@
|
|
1
|
-
# Tried desperately to monkeypatch the polymorphic connection object,
|
2
|
-
# but rails autoloading is too clever by half. Pull requests are welcome.
|
3
|
-
|
4
1
|
require 'active_support/concern'
|
5
2
|
|
6
3
|
module WithAdvisoryLock
|
7
4
|
module Concern
|
8
5
|
extend ActiveSupport::Concern
|
9
|
-
|
10
6
|
delegate :with_advisory_lock, :advisory_lock_exists?, to: 'self.class'
|
11
7
|
|
12
8
|
module ClassMethods
|
13
9
|
def with_advisory_lock(lock_name, timeout_seconds=nil, &block)
|
10
|
+
result = with_advisory_lock_result(lock_name, timeout_seconds, &block)
|
11
|
+
result.lock_was_acquired? ? result.result : false
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_advisory_lock_result(lock_name, timeout_seconds=nil, &block)
|
14
15
|
impl = impl_class.new(connection, lock_name, timeout_seconds)
|
15
16
|
impl.with_advisory_lock_if_needed(&block)
|
16
17
|
end
|
17
18
|
|
18
19
|
def advisory_lock_exists?(lock_name)
|
19
20
|
impl = impl_class.new(connection, lock_name, 0)
|
20
|
-
impl.already_locked? || !impl.yield_with_lock
|
21
|
+
impl.already_locked? || !impl.yield_with_lock.lock_was_acquired?
|
21
22
|
end
|
22
23
|
|
23
24
|
def current_advisory_lock
|
@@ -27,12 +28,12 @@ module WithAdvisoryLock
|
|
27
28
|
private
|
28
29
|
|
29
30
|
def impl_class
|
30
|
-
|
31
|
-
|
31
|
+
adapter = WithAdvisoryLock::DatabaseAdapterSupport.new(connection)
|
32
|
+
if adapter.postgresql?
|
32
33
|
WithAdvisoryLock::PostgreSQL
|
33
|
-
|
34
|
+
elsif adapter.mysql?
|
34
35
|
WithAdvisoryLock::MySQL
|
35
|
-
else
|
36
|
+
else
|
36
37
|
WithAdvisoryLock::Flock
|
37
38
|
end
|
38
39
|
end
|
@@ -7,20 +7,15 @@ module WithAdvisoryLock
|
|
7
7
|
"MySQL doesn't support nested Advisory Locks",
|
8
8
|
lock_stack.dup)
|
9
9
|
end
|
10
|
-
#
|
11
|
-
# 0 if the attempt timed out (for example, because another client has
|
12
|
-
# previously locked the name), or NULL if an error occurred
|
13
|
-
# (such as running out of memory or the thread was killed with mysqladmin kill).
|
14
|
-
sql = "SELECT GET_LOCK(#{quoted_lock_str}, 0) #{query_cache_buster}"
|
15
|
-
connection.select_value(sql).to_i > 0
|
10
|
+
execute_successful?("GET_LOCK(#{quoted_lock_str}, 0)")
|
16
11
|
end
|
17
12
|
|
18
13
|
def release_lock
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
sql = "SELECT
|
14
|
+
execute_successful?("RELEASE_LOCK(#{quoted_lock_str})")
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute_successful?(mysql_function)
|
18
|
+
sql = "SELECT #{mysql_function} AS #{unique_column_name}"
|
24
19
|
connection.select_value(sql).to_i > 0
|
25
20
|
end
|
26
21
|
|
@@ -2,15 +2,18 @@ module WithAdvisoryLock
|
|
2
2
|
class PostgreSQL < Base
|
3
3
|
# See http://www.postgresql.org/docs/9.1/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
|
4
4
|
def try_lock
|
5
|
-
|
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
|
5
|
+
execute_successful?('pg_try_advisory_lock')
|
9
6
|
end
|
10
7
|
|
11
8
|
def release_lock
|
12
|
-
|
13
|
-
|
9
|
+
execute_successful?('pg_advisory_unlock')
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_successful?(pg_function)
|
13
|
+
sql = "SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name}"
|
14
|
+
result = connection.select_value(sql)
|
15
|
+
# MRI returns 't', jruby returns true. YAY!
|
16
|
+
(result == 't' || result == true)
|
14
17
|
end
|
15
18
|
|
16
19
|
# PostgreSQL wants 2 32bit integers as the lock key.
|
data/test/lock_test.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
require 'minitest_helper'
|
2
2
|
|
3
3
|
describe 'class methods' do
|
4
|
-
let(:lock_name) {
|
4
|
+
let(:lock_name) { 'test lock' }
|
5
5
|
|
6
6
|
describe '.current_advisory_lock' do
|
7
|
-
it
|
7
|
+
it 'returns nil outside an advisory lock request' do
|
8
8
|
Tag.current_advisory_lock.must_be_nil
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'returns the name of the last lock acquired' do
|
12
12
|
Tag.with_advisory_lock(lock_name) do
|
13
|
+
# The lock name may have a prefix if WITH_ADVISORY_LOCK_PREFIX env is set
|
13
14
|
Tag.current_advisory_lock.must_match /#{lock_name}/
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
19
|
describe '.advisory_lock_exists?' do
|
19
|
-
it
|
20
|
+
it 'returns false for an unacquired lock' do
|
20
21
|
Tag.advisory_lock_exists?(lock_name).must_be_false
|
21
22
|
end
|
22
23
|
|
@@ -27,13 +28,12 @@ describe 'class methods' do
|
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
describe
|
31
|
+
describe 'zero timeout_seconds' do
|
31
32
|
it 'attempts the lock exactly once with no timeout' do
|
32
|
-
|
33
|
+
expected = SecureRandom.base64
|
33
34
|
Tag.with_advisory_lock(lock_name, 0) do
|
34
|
-
|
35
|
-
end
|
36
|
-
block_was_yielded.must_be_true
|
35
|
+
expected
|
36
|
+
end.must_equal expected
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
data/test/minitest_helper.rb
CHANGED
@@ -4,14 +4,14 @@ require 'with_advisory_lock'
|
|
4
4
|
require 'tmpdir'
|
5
5
|
require 'securerandom'
|
6
6
|
|
7
|
-
db_config = File.expand_path("database.yml", File.dirname(__FILE__))
|
8
|
-
ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(db_config)).result)
|
9
|
-
|
10
7
|
def env_db
|
11
|
-
(ENV[
|
8
|
+
(ENV['DB'] || :mysql).to_sym
|
12
9
|
end
|
13
10
|
|
14
|
-
|
11
|
+
db_config = File.expand_path('database.yml', File.dirname(__FILE__))
|
12
|
+
ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(db_config)).result)
|
13
|
+
|
14
|
+
ENV['WITH_ADVISORY_LOCK_PREFIX'] ||= SecureRandom.hex
|
15
15
|
|
16
16
|
ActiveRecord::Base.establish_connection(env_db)
|
17
17
|
ActiveRecord::Migration.verbose = false
|
@@ -19,14 +19,18 @@ ActiveRecord::Migration.verbose = false
|
|
19
19
|
require 'test_models'
|
20
20
|
begin
|
21
21
|
require 'minitest'
|
22
|
-
rescue LoadError
|
22
|
+
rescue LoadError
|
23
|
+
puts 'Failed to load the minitest gem; built-in version will be used.'
|
23
24
|
end
|
24
25
|
require 'minitest/autorun'
|
25
26
|
require 'minitest/great_expectations'
|
27
|
+
if ActiveRecord::VERSION::MAJOR > 3
|
28
|
+
# minitest-reporters-1.0.5/lib/minitest/old_activesupport_fix.rb:7:in `remove_method': method `run' not defined in ActiveSupport::Testing::SetupAndTeardown::ForMinitest (NameError)
|
29
|
+
require 'minitest/reporters'
|
30
|
+
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
31
|
+
end
|
26
32
|
require 'mocha/setup'
|
27
33
|
|
28
|
-
Thread.abort_on_exception = true
|
29
|
-
|
30
34
|
class MiniTest::Spec
|
31
35
|
before do
|
32
36
|
ENV['FLOCK_DIR'] = Dir.mktmpdir
|
data/test/parallelism_test.rb
CHANGED
@@ -1,45 +1,52 @@
|
|
1
1
|
require 'minitest_helper'
|
2
|
+
require 'forwardable'
|
2
3
|
|
3
|
-
describe
|
4
|
+
describe 'parallelism' do
|
4
5
|
class FindOrCreateWorker
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@thread, :join, :wakeup, :join, :status, :to_s
|
8
|
+
|
9
|
+
def initialize(name, use_advisory_lock)
|
10
|
+
@name = name
|
11
|
+
@use_advisory_lock = use_advisory_lock
|
12
|
+
@thread = Thread.new { work_later }
|
13
|
+
end
|
14
|
+
|
15
|
+
def work_later
|
16
|
+
sleep
|
17
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
18
|
+
if @use_advisory_lock
|
19
|
+
Tag.with_advisory_lock(@name) { work }
|
20
|
+
else
|
21
|
+
work
|
14
22
|
end
|
15
23
|
end
|
16
24
|
end
|
17
25
|
|
18
|
-
def work
|
26
|
+
def work
|
19
27
|
Tag.transaction do
|
20
|
-
Tag.where(name: name).first_or_create
|
28
|
+
Tag.where(name: @name).first_or_create
|
21
29
|
end
|
22
30
|
end
|
23
|
-
|
24
|
-
def join
|
25
|
-
@thread.join
|
26
|
-
end
|
27
31
|
end
|
28
32
|
|
29
33
|
def run_workers
|
30
|
-
all_workers = []
|
31
34
|
@names = @iterations.times.map { |iter| "iteration ##{iter}" }
|
32
35
|
@names.each do |name|
|
33
|
-
wake_time = 1.second.from_now
|
34
36
|
workers = @workers.times.map do
|
35
|
-
FindOrCreateWorker.new(
|
37
|
+
FindOrCreateWorker.new(name, @use_advisory_lock)
|
38
|
+
end
|
39
|
+
# Wait for all the threads to get ready:
|
40
|
+
until workers.all? { |ea| ea.status == 'sleep' }
|
41
|
+
sleep(0.1)
|
36
42
|
end
|
43
|
+
# OK, GO!
|
44
|
+
workers.each(&:wakeup)
|
45
|
+
# Then wait for them to finish:
|
37
46
|
workers.each(&:join)
|
38
|
-
all_workers += workers
|
39
47
|
end
|
40
48
|
# Ensure we're still connected:
|
41
49
|
ActiveRecord::Base.connection_pool.connection
|
42
|
-
all_workers
|
43
50
|
end
|
44
51
|
|
45
52
|
before :each do
|
@@ -47,14 +54,14 @@ describe "parallelism" do
|
|
47
54
|
@workers = 10
|
48
55
|
end
|
49
56
|
|
50
|
-
it
|
57
|
+
it 'creates multiple duplicate rows without advisory locks' do
|
51
58
|
@use_advisory_lock = false
|
52
59
|
@iterations = 1
|
53
60
|
run_workers
|
54
61
|
Tag.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
|
55
62
|
TagAudit.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
|
56
63
|
Label.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
|
57
|
-
end unless env_db == :sqlite
|
64
|
+
end unless env_db == :sqlite # < SQLite, understandably, throws "The database file is locked (database is locked)"
|
58
65
|
|
59
66
|
it "doesn't create multiple duplicate rows with advisory locks" do
|
60
67
|
@use_advisory_lock = true
|
@@ -66,50 +73,3 @@ describe "parallelism" do
|
|
66
73
|
end
|
67
74
|
end
|
68
75
|
|
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
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# Wait for the thread to acquire the lock:
|
87
|
-
sleep(0.1)
|
88
|
-
ActiveRecord::Base.connection.reconnect!
|
89
|
-
end
|
90
|
-
|
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
|
99
|
-
|
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
|
103
|
-
|
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'
|
107
|
-
|
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
|
114
|
-
end
|
115
|
-
end
|
data/test/thread_test.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe 'separate thread tests' do
|
4
|
+
let(:lock_name) { 'testing 1,2,3' } # OMG COMMAS
|
5
|
+
|
6
|
+
before do
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@t1_acquired_lock = false
|
9
|
+
@t1_return_value = nil
|
10
|
+
|
11
|
+
@t1 = Thread.new do
|
12
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
13
|
+
@t1_return_value = Label.with_advisory_lock(lock_name) do
|
14
|
+
@mutex.synchronize { @t1_acquired_lock = true }
|
15
|
+
sleep
|
16
|
+
't1 finished'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Wait for the thread to acquire the lock:
|
22
|
+
until @mutex.synchronize { @t1_acquired_lock } do
|
23
|
+
sleep(0.1)
|
24
|
+
end
|
25
|
+
ActiveRecord::Base.connection.reconnect!
|
26
|
+
end
|
27
|
+
|
28
|
+
after do
|
29
|
+
@t1.wakeup if @t1.status == 'sleep'
|
30
|
+
@t1.join
|
31
|
+
end
|
32
|
+
|
33
|
+
it '#with_advisory_lock with a 0 timeout returns false immediately' do
|
34
|
+
response = Label.with_advisory_lock(lock_name, 0) do
|
35
|
+
fail 'should not be yielded to'
|
36
|
+
end
|
37
|
+
response.must_be_false
|
38
|
+
end
|
39
|
+
|
40
|
+
it '#with_advisory_lock yields to the provided block' do
|
41
|
+
@t1_acquired_lock.must_be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it '#advisory_lock_exists? returns true when another thread has the lock' do
|
45
|
+
Tag.advisory_lock_exists?(lock_name).must_be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'can re-establish the lock after the other thread releases it' do
|
49
|
+
@t1.wakeup
|
50
|
+
@t1.join
|
51
|
+
@t1_return_value.must_equal 't1 finished'
|
52
|
+
|
53
|
+
# We should now be able to acquire the lock immediately:
|
54
|
+
reacquired = false
|
55
|
+
Label.with_advisory_lock(lock_name, 0) do
|
56
|
+
reacquired = true
|
57
|
+
end.must_be_true
|
58
|
+
reacquired.must_be_true
|
59
|
+
end
|
60
|
+
end
|
data/tests.sh
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
#!/bin/bash -e
|
2
2
|
export DB
|
3
3
|
|
4
|
-
for RUBY in 2.1.2 jruby-1.7.
|
4
|
+
for RUBY in 2.1.2 jruby-1.7.13 ; do
|
5
5
|
rbenv local $RUBY
|
6
6
|
for DB in mysql postgresql sqlite ; do
|
7
7
|
echo "$DB | $(ruby -v)"
|
8
|
-
appraisal bundle update
|
9
|
-
appraisal rake test
|
8
|
+
# appraisal bundle update
|
9
|
+
appraisal rake test --verbose
|
10
10
|
done
|
11
11
|
done
|
data/with_advisory_lock.gemspec
CHANGED
@@ -19,11 +19,12 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.require_paths = %w(lib)
|
20
20
|
|
21
21
|
gem.add_runtime_dependency 'activerecord', '>= 3.2'
|
22
|
+
gem.add_runtime_dependency 'thread_safe'
|
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
|
+
gem.add_development_dependency 'minitest-reporters'
|
27
28
|
gem.add_development_dependency 'mocha'
|
28
29
|
gem.add_development_dependency 'appraisal'
|
29
30
|
end
|
metadata
CHANGED
@@ -1,111 +1,125 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: with_advisory_lock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: activerecord
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
16
15
|
requirements:
|
17
|
-
- -
|
16
|
+
- - '>='
|
18
17
|
- !ruby/object:Gem::Version
|
19
18
|
version: '3.2'
|
20
|
-
|
19
|
+
name: activerecord
|
21
20
|
prerelease: false
|
21
|
+
type: :runtime
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
|
-
- -
|
30
|
+
- - '>='
|
32
31
|
- !ruby/object:Gem::Version
|
33
32
|
version: '0'
|
34
|
-
|
33
|
+
name: thread_safe
|
35
34
|
prerelease: false
|
35
|
+
type: :runtime
|
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
|
-
name: yard
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
44
43
|
requirements:
|
45
|
-
- -
|
44
|
+
- - '>='
|
46
45
|
- !ruby/object:Gem::Version
|
47
46
|
version: '0'
|
48
|
-
|
47
|
+
name: yard
|
49
48
|
prerelease: false
|
49
|
+
type: :development
|
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
|
-
name: minitest
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
58
57
|
requirements:
|
59
|
-
- -
|
58
|
+
- - '>='
|
60
59
|
- !ruby/object:Gem::Version
|
61
60
|
version: '0'
|
62
|
-
|
61
|
+
name: minitest
|
63
62
|
prerelease: false
|
63
|
+
type: :development
|
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
|
-
name: minitest-great_expectations
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
72
71
|
requirements:
|
73
|
-
- -
|
72
|
+
- - '>='
|
74
73
|
- !ruby/object:Gem::Version
|
75
74
|
version: '0'
|
76
|
-
|
75
|
+
name: minitest-great_expectations
|
77
76
|
prerelease: false
|
77
|
+
type: :development
|
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
|
-
name: mocha
|
85
84
|
requirement: !ruby/object:Gem::Requirement
|
86
85
|
requirements:
|
87
|
-
- -
|
86
|
+
- - '>='
|
88
87
|
- !ruby/object:Gem::Version
|
89
88
|
version: '0'
|
90
|
-
|
89
|
+
name: minitest-reporters
|
91
90
|
prerelease: false
|
91
|
+
type: :development
|
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: appraisal
|
99
98
|
requirement: !ruby/object:Gem::Requirement
|
100
99
|
requirements:
|
101
|
-
- -
|
100
|
+
- - '>='
|
102
101
|
- !ruby/object:Gem::Version
|
103
102
|
version: '0'
|
103
|
+
name: mocha
|
104
|
+
prerelease: false
|
104
105
|
type: :development
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
name: appraisal
|
105
118
|
prerelease: false
|
119
|
+
type: :development
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
|
-
- -
|
122
|
+
- - '>='
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
111
125
|
description: Advisory locking for ActiveRecord
|
@@ -115,8 +129,8 @@ executables: []
|
|
115
129
|
extensions: []
|
116
130
|
extra_rdoc_files: []
|
117
131
|
files:
|
118
|
-
-
|
119
|
-
-
|
132
|
+
- .gitignore
|
133
|
+
- .travis.yml
|
120
134
|
- Appraisals
|
121
135
|
- Gemfile
|
122
136
|
- LICENSE.txt
|
@@ -142,30 +156,31 @@ files:
|
|
142
156
|
- test/nesting_test.rb
|
143
157
|
- test/parallelism_test.rb
|
144
158
|
- test/test_models.rb
|
159
|
+
- test/thread_test.rb
|
145
160
|
- tests.sh
|
146
161
|
- with_advisory_lock.gemspec
|
147
162
|
homepage: https://github.com/mceachen/with_advisory_lock
|
148
163
|
licenses:
|
149
164
|
- MIT
|
150
165
|
metadata: {}
|
151
|
-
post_install_message:
|
166
|
+
post_install_message:
|
152
167
|
rdoc_options: []
|
153
168
|
require_paths:
|
154
169
|
- lib
|
155
170
|
required_ruby_version: !ruby/object:Gem::Requirement
|
156
171
|
requirements:
|
157
|
-
- -
|
172
|
+
- - '>='
|
158
173
|
- !ruby/object:Gem::Version
|
159
174
|
version: '0'
|
160
175
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
176
|
requirements:
|
162
|
-
- -
|
177
|
+
- - '>='
|
163
178
|
- !ruby/object:Gem::Version
|
164
179
|
version: '0'
|
165
180
|
requirements: []
|
166
|
-
rubyforge_project:
|
167
|
-
rubygems_version: 2.
|
168
|
-
signing_key:
|
181
|
+
rubyforge_project:
|
182
|
+
rubygems_version: 2.1.9
|
183
|
+
signing_key:
|
169
184
|
specification_version: 4
|
170
185
|
summary: Advisory locking for ActiveRecord
|
171
186
|
test_files:
|
@@ -176,4 +191,5 @@ test_files:
|
|
176
191
|
- test/nesting_test.rb
|
177
192
|
- test/parallelism_test.rb
|
178
193
|
- test/test_models.rb
|
179
|
-
|
194
|
+
- test/thread_test.rb
|
195
|
+
has_rdoc:
|