with_advisory_lock 4.6.0 → 5.3.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci-mysql5.yml +61 -0
  3. data/.github/workflows/ci-mysql8.yml +62 -0
  4. data/.github/workflows/ci-postgresql.yml +64 -0
  5. data/.github/workflows/ci-sqlite3.yml +54 -0
  6. data/.github/workflows/release.yml +17 -0
  7. data/.gitignore +2 -0
  8. data/.release-please-manifest.json +1 -0
  9. data/.ruby-version +2 -0
  10. data/.tool-versions +1 -1
  11. data/Appraisals +34 -18
  12. data/CHANGELOG.md +50 -0
  13. data/Gemfile +0 -12
  14. data/LICENSE.txt +4 -4
  15. data/Makefile +14 -0
  16. data/README.md +17 -6
  17. data/docker-compose.yml +20 -0
  18. data/gemfiles/{activerecord_6.0.gemfile → activerecord_6.1.gemfile} +4 -2
  19. data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.0.gemfile} +4 -2
  20. data/gemfiles/activerecord_7.1.gemfile +14 -0
  21. data/lib/with_advisory_lock/base.rb +18 -4
  22. data/lib/with_advisory_lock/concern.rb +17 -17
  23. data/lib/with_advisory_lock/database_adapter_support.rb +7 -47
  24. data/lib/with_advisory_lock/failed_to_acquire_lock.rb +9 -0
  25. data/lib/with_advisory_lock/flock.rb +4 -3
  26. data/lib/with_advisory_lock/mysql.rb +12 -7
  27. data/lib/with_advisory_lock/postgresql.rb +43 -18
  28. data/lib/with_advisory_lock/version.rb +3 -1
  29. data/lib/with_advisory_lock.rb +9 -10
  30. data/release-please-config.json +9 -0
  31. data/test/test_helper.rb +66 -0
  32. data/test/test_models.rb +16 -10
  33. data/test/with_advisory_lock/base_test.rb +9 -0
  34. data/test/with_advisory_lock/concern_test.rb +33 -0
  35. data/test/with_advisory_lock/lock_test.rb +111 -0
  36. data/test/with_advisory_lock/nesting_test.rb +28 -0
  37. data/test/with_advisory_lock/options_test.rb +66 -0
  38. data/test/with_advisory_lock/parallelism_test.rb +75 -0
  39. data/test/with_advisory_lock/shared_test.rb +134 -0
  40. data/test/with_advisory_lock/thread_test.rb +61 -0
  41. data/test/with_advisory_lock/transaction_test.rb +68 -0
  42. data/with_advisory_lock.gemspec +29 -23
  43. metadata +56 -47
  44. data/.travis.yml +0 -38
  45. data/gemfiles/activerecord_4.2.gemfile +0 -19
  46. data/gemfiles/activerecord_5.0.gemfile +0 -19
  47. data/gemfiles/activerecord_5.1.gemfile +0 -19
  48. data/lib/with_advisory_lock/mysql_no_nesting.rb +0 -20
  49. data/lib/with_advisory_lock/nested_advisory_lock_error.rb +0 -14
  50. data/test/concern_test.rb +0 -20
  51. data/test/database.yml +0 -17
  52. data/test/lock_test.rb +0 -47
  53. data/test/minitest_helper.rb +0 -40
  54. data/test/nesting_test.rb +0 -93
  55. data/test/options_test.rb +0 -64
  56. data/test/parallelism_test.rb +0 -77
  57. data/test/shared_test.rb +0 -131
  58. data/test/thread_test.rb +0 -60
  59. data/test/transaction_test.rb +0 -70
  60. data/tests.sh +0 -11
@@ -1,19 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 4.2.0"
6
-
7
- platforms :ruby do
8
- gem "mysql2", "< 0.5"
9
- gem "pg", "~> 0.21"
10
- gem "sqlite3", "~> 1.3.6"
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: "../"
@@ -1,19 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 5.0.0"
6
-
7
- platforms :ruby do
8
- gem "mysql2"
9
- gem "pg"
10
- gem "sqlite3", "~> 1.3.6"
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: "../"
@@ -1,19 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 5.1.0"
6
-
7
- platforms :ruby do
8
- gem "mysql2"
9
- gem "pg"
10
- gem "sqlite3", "~> 1.3.6"
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: "../"
@@ -1,20 +0,0 @@
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,14 +0,0 @@
1
- module WithAdvisoryLock
2
- class NestedAdvisoryLockError < StandardError
3
- attr_accessor :lock_stack
4
-
5
- def initialize(msg = nil, lock_stack = nil)
6
- super(msg)
7
- @lock_stack = lock_stack
8
- end
9
-
10
- def to_s
11
- super + (lock_stack ? ": lock stack = #{lock_stack}" : '')
12
- end
13
- end
14
- end
data/test/concern_test.rb DELETED
@@ -1,20 +0,0 @@
1
- require 'minitest_helper'
2
-
3
- describe "with_advisory_lock.concern" do
4
- it "adds with_advisory_lock to ActiveRecord classes" do
5
- assert Tag.respond_to?(:with_advisory_lock)
6
- end
7
-
8
- it "adds with_advisory_lock to ActiveRecord instances" do
9
- assert Label.new.respond_to?(:with_advisory_lock)
10
- end
11
-
12
- it "adds advisory_lock_exists? to ActiveRecord classes" do
13
- assert Tag.respond_to?(:advisory_lock_exists?)
14
- end
15
-
16
- it "adds advisory_lock_exists? to ActiveRecord classes" do
17
- assert Label.new.respond_to?(:advisory_lock_exists?)
18
- end
19
-
20
- end
data/test/database.yml DELETED
@@ -1,17 +0,0 @@
1
- sqlite:
2
- adapter: <%= "jdbc" if defined? JRUBY_VERSION %>sqlite3
3
- database: test/sqlite.db
4
- timeout: 500
5
- pool: 50
6
- postgresql:
7
- adapter: postgresql
8
- username: postgres
9
- database: with_advisory_lock_test
10
- min_messages: ERROR
11
- pool: 50
12
- mysql:
13
- adapter: mysql2
14
- host: localhost
15
- username: root
16
- database: with_advisory_lock_test
17
- pool: 50
data/test/lock_test.rb DELETED
@@ -1,47 +0,0 @@
1
- require 'minitest_helper'
2
-
3
- describe 'class methods' do
4
- let(:lock_name) { 'test lock' }
5
-
6
- describe '.current_advisory_lock' do
7
- it 'returns nil outside an advisory lock request' do
8
- Tag.current_advisory_lock.must_be_nil
9
- end
10
-
11
- it 'returns the name of the last lock acquired' do
12
- Tag.with_advisory_lock(lock_name) do
13
- # The lock name may have a prefix if WITH_ADVISORY_LOCK_PREFIX env is set
14
- Tag.current_advisory_lock.must_match(/#{lock_name}/)
15
- end
16
- end
17
-
18
- it 'can obtain a lock with a name that attempts to disrupt a SQL comment' do
19
- dangerous_lock_name = 'test */ lock /*'
20
- Tag.with_advisory_lock(dangerous_lock_name) do
21
- Tag.current_advisory_lock.must_match(/#{Regexp.escape(dangerous_lock_name)}/)
22
- end
23
-
24
- end
25
- end
26
-
27
- describe '.advisory_lock_exists?' do
28
- it 'returns false for an unacquired lock' do
29
- Tag.advisory_lock_exists?(lock_name).must_be_false
30
- end
31
-
32
- it 'returns the name of the last lock acquired' do
33
- Tag.with_advisory_lock(lock_name) do
34
- Tag.advisory_lock_exists?(lock_name).must_be_true
35
- end
36
- end
37
- end
38
-
39
- describe 'zero timeout_seconds' do
40
- it 'attempts the lock exactly once with no timeout' do
41
- expected = SecureRandom.base64
42
- Tag.with_advisory_lock(lock_name, 0) do
43
- expected
44
- end.must_equal expected
45
- end
46
- end
47
- end
@@ -1,40 +0,0 @@
1
- require 'erb'
2
- require 'active_record'
3
- require 'with_advisory_lock'
4
- require 'tmpdir'
5
- require 'securerandom'
6
-
7
- def env_db
8
- (ENV['DB'] || :mysql).to_sym
9
- end
10
-
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
-
16
- ActiveRecord::Base.establish_connection(env_db)
17
- ActiveRecord::Migration.verbose = false
18
-
19
- require 'test_models'
20
- begin
21
- require 'minitest'
22
- rescue LoadError
23
- puts 'Failed to load the minitest gem; built-in version will be used.'
24
- end
25
- require 'minitest/autorun'
26
- require 'minitest/great_expectations'
27
- require 'mocha/setup'
28
-
29
- class MiniTest::Spec
30
- before do
31
- ENV['FLOCK_DIR'] = Dir.mktmpdir
32
- Tag.delete_all
33
- TagAudit.delete_all
34
- Label.delete_all
35
- end
36
- after do
37
- FileUtils.remove_entry_secure ENV['FLOCK_DIR']
38
- end
39
- end
40
-
data/test/nesting_test.rb DELETED
@@ -1,93 +0,0 @@
1
- require 'minitest_helper'
2
-
3
- describe "lock nesting" do
4
- # This simplifies what we expect from the lock name:
5
- before :each do
6
- @prior_prefix = ENV['WITH_ADVISORY_LOCK_PREFIX']
7
- ENV['WITH_ADVISORY_LOCK_PREFIX'] = nil
8
- end
9
-
10
- after :each do
11
- ENV['WITH_ADVISORY_LOCK_PREFIX'] = @prior_prefix
12
- end
13
-
14
- it "doesn't request the same lock twice" do
15
- impl = WithAdvisoryLock::Base.new(nil, nil, nil)
16
- impl.lock_stack.must_be_empty
17
- Tag.with_advisory_lock("first") do
18
- impl.lock_stack.map(&:name).must_equal %w(first)
19
- # Even MySQL should be OK with this:
20
- Tag.with_advisory_lock("first") do
21
- impl.lock_stack.map(&:name).must_equal %w(first)
22
- end
23
- impl.lock_stack.map(&:name).must_equal %w(first)
24
- end
25
- impl.lock_stack.must_be_empty
26
- end
27
-
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
- exc = proc {
31
- Tag.with_advisory_lock("first") do
32
- Tag.with_advisory_lock("second") do
33
- end
34
- end
35
- }.must_raise WithAdvisoryLock::NestedAdvisoryLockError
36
- exc.lock_stack.map(&:name).must_equal %w(first)
37
- end
38
-
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'
63
- impl = WithAdvisoryLock::Base.new(nil, nil, nil)
64
- impl.lock_stack.must_be_empty
65
- Tag.with_advisory_lock("first") do
66
- impl.lock_stack.map(&:name).must_equal %w(first)
67
- Tag.with_advisory_lock("second") do
68
- impl.lock_stack.map(&:name).must_equal %w(first second)
69
- Tag.with_advisory_lock("first") do
70
- # Shouldn't ask for another lock:
71
- impl.lock_stack.map(&:name).must_equal %w(first second)
72
- Tag.with_advisory_lock("second") do
73
- # Shouldn't ask for another lock:
74
- impl.lock_stack.map(&:name).must_equal %w(first second)
75
- end
76
- end
77
- end
78
- impl.lock_stack.map(&:name).must_equal %w(first)
79
- end
80
- impl.lock_stack.must_be_empty
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
93
- end
data/test/options_test.rb DELETED
@@ -1,64 +0,0 @@
1
- require 'minitest_helper'
2
-
3
- describe 'options parsing' do
4
- def parse_options(options)
5
- WithAdvisoryLock::Base.new(mock, mock, options)
6
- end
7
-
8
- specify 'defaults (empty hash)' do
9
- impl = parse_options({})
10
- impl.timeout_seconds.must_be_nil
11
- impl.shared.must_equal false
12
- impl.transaction.must_equal false
13
- end
14
-
15
- specify 'nil sets timeout to nil' do
16
- impl = parse_options(nil)
17
- impl.timeout_seconds.must_be_nil
18
- impl.shared.must_equal false
19
- impl.transaction.must_equal false
20
- end
21
-
22
- specify 'integer sets timeout to value' do
23
- impl = parse_options(42)
24
- impl.timeout_seconds.must_equal 42
25
- impl.shared.must_equal false
26
- impl.transaction.must_equal false
27
- end
28
-
29
- specify 'hash with invalid key errors' do
30
- proc {
31
- parse_options(foo: 42)
32
- }.must_raise ArgumentError
33
- end
34
-
35
- specify 'hash with timeout_seconds sets timeout to value' do
36
- impl = parse_options(timeout_seconds: 123)
37
- impl.timeout_seconds.must_equal 123
38
- impl.shared.must_equal false
39
- impl.transaction.must_equal false
40
- end
41
-
42
- specify 'hash with shared option sets shared to true' do
43
- impl = parse_options(shared: true)
44
- impl.timeout_seconds.must_be_nil
45
- impl.shared.must_equal true
46
- impl.transaction.must_equal false
47
- end
48
-
49
- specify 'hash with transaction option set transaction to true' do
50
- impl = parse_options(transaction: true)
51
- impl.timeout_seconds.must_be_nil
52
- impl.shared.must_equal false
53
- impl.transaction.must_equal true
54
- end
55
-
56
- specify 'hash with multiple keys sets options' do
57
- foo = mock
58
- bar = mock
59
- impl = parse_options(timeout_seconds: foo, shared: bar)
60
- impl.timeout_seconds.must_equal foo
61
- impl.shared.must_equal bar
62
- impl.transaction.must_equal false
63
- end
64
- end
@@ -1,77 +0,0 @@
1
- require 'minitest_helper'
2
- require 'forwardable'
3
-
4
- describe 'parallelism' do
5
- class FindOrCreateWorker
6
- extend Forwardable
7
- def_delegators :@thread, :join, :wakeup, :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
22
- end
23
- end
24
- end
25
-
26
- def work
27
- Tag.transaction do
28
- Tag.where(name: @name).first_or_create
29
- end
30
- end
31
- end
32
-
33
- def run_workers
34
- @names = @iterations.times.map { |iter| "iteration ##{iter}" }
35
- @names.each do |name|
36
- workers = @workers.times.map do
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)
42
- end
43
- # OK, GO!
44
- workers.each(&:wakeup)
45
- # Then wait for them to finish:
46
- workers.each(&:join)
47
- end
48
- # Ensure we're still connected:
49
- ActiveRecord::Base.connection_pool.connection
50
- end
51
-
52
- before :each do
53
- ActiveRecord::Base.connection.reconnect!
54
- @workers = 10
55
- end
56
-
57
- # < SQLite, understandably, throws "The database file is locked (database is locked)"
58
-
59
- it 'creates multiple duplicate rows without advisory locks' do
60
- skip if env_db == :sqlite
61
- @use_advisory_lock = false
62
- @iterations = 1
63
- run_workers
64
- Tag.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
65
- TagAudit.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
66
- Label.all.size.must_be :>, @iterations # <- any duplicated rows will make me happy.
67
- end
68
-
69
- it "doesn't create multiple duplicate rows with advisory locks" do
70
- @use_advisory_lock = true
71
- @iterations = 10
72
- run_workers
73
- Tag.all.size.must_equal @iterations # <- any duplicated rows will NOT make me happy.
74
- TagAudit.all.size.must_equal @iterations # <- any duplicated rows will NOT make me happy.
75
- Label.all.size.must_equal @iterations # <- any duplicated rows will NOT make me happy.
76
- end
77
- end
data/test/shared_test.rb DELETED
@@ -1,131 +0,0 @@
1
- require 'minitest_helper'
2
-
3
- describe 'shared locks' do
4
- def supported?
5
- env_db != :mysql
6
- end
7
-
8
- class SharedTestWorker
9
- def initialize(shared)
10
- @shared = shared
11
-
12
- @locked = nil
13
- @cleanup = false
14
- @thread = Thread.new { work }
15
- end
16
-
17
- def locked?
18
- sleep 0.01 while @locked.nil? && @thread.alive?
19
- @locked
20
- end
21
-
22
- def cleanup!
23
- @cleanup = true
24
- @thread.join
25
- raise if @thread.status.nil?
26
- end
27
-
28
- private
29
-
30
- def work
31
- ActiveRecord::Base.connection_pool.with_connection do
32
- Tag.with_advisory_lock('test', timeout_seconds: 0, shared: @shared) do
33
- @locked = true
34
- sleep 0.01 until @cleanup
35
- end
36
- @locked = false
37
- sleep 0.01 until @cleanup
38
- end
39
- end
40
- end
41
-
42
- it 'does not allow two exclusive locks' do
43
- one = SharedTestWorker.new(false)
44
- one.locked?.must_equal true
45
-
46
- two = SharedTestWorker.new(false)
47
- two.locked?.must_equal false
48
-
49
- one.cleanup!
50
- two.cleanup!
51
- end
52
-
53
- describe 'not supported' do
54
- before do
55
- skip if supported?
56
- end
57
-
58
- it 'raises an error when attempting to use a shared lock' do
59
- one = SharedTestWorker.new(true)
60
- one.locked?.must_be_nil
61
- exception = proc {
62
- one.cleanup!
63
- }.must_raise ArgumentError
64
- exception.message.must_include 'not supported'
65
- end
66
- end
67
-
68
- describe 'supported' do
69
- before do
70
- skip unless supported?
71
- end
72
-
73
- it 'does allow two shared locks' do
74
- one = SharedTestWorker.new(true)
75
- one.locked?.must_equal true
76
-
77
- two = SharedTestWorker.new(true)
78
- two.locked?.must_equal true
79
-
80
- one.cleanup!
81
- two.cleanup!
82
- end
83
-
84
- it 'does not allow exclusive lock with shared lock' do
85
- one = SharedTestWorker.new(true)
86
- one.locked?.must_equal true
87
-
88
- two = SharedTestWorker.new(false)
89
- two.locked?.must_equal false
90
-
91
- three = SharedTestWorker.new(true)
92
- three.locked?.must_equal true
93
-
94
- one.cleanup!
95
- two.cleanup!
96
- three.cleanup!
97
- end
98
-
99
- it 'does not allow shared lock with exclusive lock' do
100
- one = SharedTestWorker.new(false)
101
- one.locked?.must_equal true
102
-
103
- two = SharedTestWorker.new(true)
104
- two.locked?.must_equal false
105
-
106
- one.cleanup!
107
- two.cleanup!
108
- end
109
-
110
- describe 'PostgreSQL' do
111
- before do
112
- skip unless env_db == :postgresql
113
- end
114
-
115
- def pg_lock_modes
116
- ActiveRecord::Base.connection.select_values("SELECT mode FROM pg_locks WHERE locktype = 'advisory';")
117
- end
118
-
119
- it 'allows shared lock to be upgraded to an exclusive lock' do
120
- pg_lock_modes.must_equal %w[]
121
- Tag.with_advisory_lock 'test', shared: true do
122
- pg_lock_modes.must_equal %w[ShareLock]
123
- Tag.with_advisory_lock 'test', shared: false do
124
- pg_lock_modes.must_equal %w[ShareLock ExclusiveLock]
125
- end
126
- end
127
- pg_lock_modes.must_equal %w[]
128
- end
129
- end
130
- end
131
- end
data/test/thread_test.rb DELETED
@@ -1,60 +0,0 @@
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