with_advisory_lock 4.6.0 → 7.0.1

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +76 -0
  3. data/.github/workflows/release.yml +17 -0
  4. data/.gitignore +2 -0
  5. data/.release-please-manifest.json +1 -0
  6. data/.ruby-version +2 -0
  7. data/.tool-versions +1 -1
  8. data/CHANGELOG.md +89 -0
  9. data/Gemfile +22 -3
  10. data/LICENSE.txt +4 -4
  11. data/Makefile +10 -0
  12. data/README.md +22 -39
  13. data/Rakefile +5 -2
  14. data/bin/console +11 -0
  15. data/bin/rails +15 -0
  16. data/bin/sanity +20 -0
  17. data/bin/sanity_check +86 -0
  18. data/bin/setup +8 -0
  19. data/bin/setup_test_db +59 -0
  20. data/bin/test_connections +22 -0
  21. data/docker-compose.yml +19 -0
  22. data/lib/with_advisory_lock/concern.rb +37 -30
  23. data/lib/with_advisory_lock/core_advisory.rb +110 -0
  24. data/lib/with_advisory_lock/failed_to_acquire_lock.rb +9 -0
  25. data/lib/with_advisory_lock/jruby_adapter.rb +29 -0
  26. data/lib/with_advisory_lock/lock_stack_item.rb +6 -0
  27. data/lib/with_advisory_lock/mysql_advisory.rb +71 -0
  28. data/lib/with_advisory_lock/postgresql_advisory.rb +112 -0
  29. data/lib/with_advisory_lock/result.rb +14 -0
  30. data/lib/with_advisory_lock/version.rb +3 -1
  31. data/lib/with_advisory_lock.rb +38 -11
  32. data/release-please-config.json +9 -0
  33. data/test/dummy/Rakefile +8 -0
  34. data/test/dummy/app/controllers/application_controller.rb +7 -0
  35. data/test/dummy/app/models/application_record.rb +6 -0
  36. data/test/dummy/app/models/label.rb +4 -0
  37. data/test/dummy/app/models/mysql_label.rb +5 -0
  38. data/test/dummy/app/models/mysql_record.rb +6 -0
  39. data/test/dummy/app/models/mysql_tag.rb +10 -0
  40. data/test/dummy/app/models/mysql_tag_audit.rb +5 -0
  41. data/test/dummy/app/models/tag.rb +8 -0
  42. data/test/dummy/app/models/tag_audit.rb +4 -0
  43. data/test/dummy/config/application.rb +31 -0
  44. data/test/dummy/config/boot.rb +3 -0
  45. data/test/dummy/config/database.yml +13 -0
  46. data/test/dummy/config/environment.rb +7 -0
  47. data/test/dummy/config/routes.rb +4 -0
  48. data/test/dummy/config.ru +6 -0
  49. data/test/dummy/db/schema.rb +15 -0
  50. data/test/dummy/db/secondary_schema.rb +15 -0
  51. data/test/dummy/lib/tasks/db.rake +40 -0
  52. data/test/sanity_check_test.rb +63 -0
  53. data/test/test_helper.rb +33 -0
  54. data/test/with_advisory_lock/concern_test.rb +79 -0
  55. data/test/with_advisory_lock/lock_test.rb +197 -0
  56. data/test/with_advisory_lock/multi_adapter_test.rb +17 -0
  57. data/test/with_advisory_lock/mysql_release_lock_test.rb +119 -0
  58. data/test/with_advisory_lock/parallelism_test.rb +101 -0
  59. data/test/with_advisory_lock/postgresql_race_condition_test.rb +118 -0
  60. data/test/with_advisory_lock/shared_test.rb +129 -0
  61. data/test/with_advisory_lock/thread_test.rb +83 -0
  62. data/test/with_advisory_lock/transaction_test.rb +83 -0
  63. data/with_advisory_lock.gemspec +54 -28
  64. metadata +83 -69
  65. data/.travis.yml +0 -38
  66. data/Appraisals +0 -29
  67. data/gemfiles/activerecord_4.2.gemfile +0 -19
  68. data/gemfiles/activerecord_5.0.gemfile +0 -19
  69. data/gemfiles/activerecord_5.1.gemfile +0 -19
  70. data/gemfiles/activerecord_5.2.gemfile +0 -19
  71. data/gemfiles/activerecord_6.0.gemfile +0 -19
  72. data/lib/with_advisory_lock/base.rb +0 -104
  73. data/lib/with_advisory_lock/database_adapter_support.rb +0 -63
  74. data/lib/with_advisory_lock/flock.rb +0 -32
  75. data/lib/with_advisory_lock/mysql.rb +0 -27
  76. data/lib/with_advisory_lock/mysql_no_nesting.rb +0 -20
  77. data/lib/with_advisory_lock/nested_advisory_lock_error.rb +0 -14
  78. data/lib/with_advisory_lock/postgresql.rb +0 -41
  79. data/test/concern_test.rb +0 -20
  80. data/test/database.yml +0 -17
  81. data/test/lock_test.rb +0 -47
  82. data/test/minitest_helper.rb +0 -40
  83. data/test/nesting_test.rb +0 -93
  84. data/test/options_test.rb +0 -64
  85. data/test/parallelism_test.rb +0 -77
  86. data/test/shared_test.rb +0 -131
  87. data/test/test_models.rb +0 -24
  88. data/test/thread_test.rb +0 -60
  89. data/test/transaction_test.rb +0 -70
  90. data/tests.sh +0 -11
@@ -1,41 +0,0 @@
1
- module WithAdvisoryLock
2
- class PostgreSQL < Base
3
- # See http://www.postgresql.org/docs/9.1/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
4
- def try_lock
5
- pg_function = "pg_try_advisory#{transaction ? '_xact' : ''}_lock#{shared ? '_shared' : ''}"
6
- execute_successful?(pg_function)
7
- end
8
-
9
- def release_lock
10
- return if transaction
11
- pg_function = "pg_advisory_unlock#{shared ? '_shared' : ''}"
12
- execute_successful?(pg_function)
13
- rescue ActiveRecord::StatementInvalid => e
14
- raise unless e.message =~ / ERROR: +current transaction is aborted,/
15
- begin
16
- connection.rollback_db_transaction
17
- execute_successful?(pg_function)
18
- ensure
19
- connection.begin_db_transaction
20
- end
21
- end
22
-
23
- def execute_successful?(pg_function)
24
- comment = lock_name.gsub(/(\/\*)|(\*\/)/, '--')
25
- sql = "SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name} /* #{comment} */"
26
- result = connection.select_value(sql)
27
- # MRI returns 't', jruby returns true. YAY!
28
- (result == 't' || result == true)
29
- end
30
-
31
- # PostgreSQL wants 2 32bit integers as the lock key.
32
- def lock_keys
33
- @lock_keys ||= begin
34
- [stable_hashcode(lock_name), ENV['WITH_ADVISORY_LOCK_PREFIX']].map do |ea|
35
- # pg advisory args must be 31 bit ints
36
- ea.to_i & 0x7fffffff
37
- end
38
- end
39
- end
40
- end
41
- 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/test_models.rb DELETED
@@ -1,24 +0,0 @@
1
- ActiveRecord::Schema.define(:version => 0) do
2
- create_table "tags", :force => true do |t|
3
- t.string "name"
4
- end
5
- create_table "tag_audits", :id => false, :force => true do |t|
6
- t.string "tag_name"
7
- end
8
- create_table "labels", :id => false, :force => true do |t|
9
- t.string "name"
10
- end
11
- end
12
-
13
- class Tag < ActiveRecord::Base
14
- after_save do
15
- TagAudit.create(tag_name: name)
16
- Label.create(name: name)
17
- end
18
- end
19
-
20
- class TagAudit < ActiveRecord::Base
21
- end
22
-
23
- class Label < ActiveRecord::Base
24
- 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
@@ -1,70 +0,0 @@
1
- require 'minitest_helper'
2
-
3
- describe 'transaction scoping' do
4
- def supported?
5
- env_db == :postgresql
6
- end
7
-
8
- describe 'not supported' do
9
- before do
10
- skip if supported?
11
- end
12
-
13
- it 'raises an error when attempting to use transaction level locks' do
14
- Tag.transaction do
15
- exception = proc {
16
- Tag.with_advisory_lock 'test', transaction: true do
17
- raise 'should not get here'
18
- end
19
- }.must_raise ArgumentError
20
- exception.message.must_include 'not supported'
21
- end
22
- end
23
- end
24
-
25
- describe 'supported' do
26
- before do
27
- skip unless env_db == :postgresql
28
- end
29
-
30
- def pg_lock_count
31
- ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM pg_locks WHERE locktype = 'advisory';").to_i
32
- end
33
-
34
- specify 'session locks release after the block executes' do
35
- Tag.transaction do
36
- pg_lock_count.must_equal 0
37
- Tag.with_advisory_lock 'test' do
38
- pg_lock_count.must_equal 1
39
- end
40
- pg_lock_count.must_equal 0
41
- end
42
- end
43
-
44
- specify 'session locks release when transaction fails inside block' do
45
- Tag.transaction do
46
- pg_lock_count.must_equal 0
47
-
48
- exception = proc {
49
- Tag.with_advisory_lock 'test' do
50
- Tag.connection.execute 'SELECT 1/0;'
51
- end
52
- }.must_raise ActiveRecord::StatementInvalid
53
- exception.message.must_include 'division by zero'
54
-
55
- pg_lock_count.must_equal 0
56
- end
57
- end
58
-
59
- specify 'transaction level locks hold until the transaction completes' do
60
- Tag.transaction do
61
- pg_lock_count.must_equal 0
62
- Tag.with_advisory_lock 'test', transaction: true do
63
- pg_lock_count.must_equal 1
64
- end
65
- pg_lock_count.must_equal 1
66
- end
67
- pg_lock_count.must_equal 0
68
- end
69
- end
70
- end