with_advisory_lock 4.0.0 → 5.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 -5
- data/.github/workflows/ci.yml +80 -0
- data/.gitignore +3 -0
- data/.tool-versions +1 -0
- data/Appraisals +37 -11
- data/CHANGELOG.md +131 -0
- data/Gemfile +0 -12
- data/README.md +85 -171
- data/gemfiles/{activerecord_5.1.gemfile → activerecord_6.1.gemfile} +4 -2
- data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.0.gemfile} +4 -2
- data/gemfiles/activerecord_7.1.gemfile +14 -0
- data/lib/with_advisory_lock/base.rb +16 -2
- data/lib/with_advisory_lock/concern.rb +13 -2
- data/lib/with_advisory_lock/database_adapter_support.rb +10 -3
- data/lib/with_advisory_lock/failed_to_acquire_lock.rb +7 -0
- data/lib/with_advisory_lock/flock.rb +4 -3
- data/lib/with_advisory_lock/mysql.rb +6 -16
- data/lib/with_advisory_lock/postgresql.rb +9 -7
- data/lib/with_advisory_lock/version.rb +3 -1
- data/lib/with_advisory_lock.rb +1 -1
- data/test/concern_test.rb +23 -10
- data/test/lock_test.rb +61 -28
- data/test/nesting_test.rb +14 -46
- data/test/options_test.rb +35 -33
- data/test/parallelism_test.rb +35 -37
- data/test/shared_test.rb +93 -90
- data/test/test_helper.rb +52 -0
- data/test/test_models.rb +9 -7
- data/test/thread_test.rb +23 -22
- data/test/transaction_test.rb +34 -36
- data/with_advisory_lock.gemspec +24 -23
- metadata +27 -41
- data/.travis.install-mysql-5.7.sh +0 -11
- data/.travis.yml +0 -32
- data/gemfiles/activerecord_4.2.gemfile +0 -19
- data/gemfiles/activerecord_5.0.gemfile +0 -19
- data/lib/with_advisory_lock/nested_advisory_lock_error.rb +0 -14
- data/test/database.yml +0 -17
- data/test/minitest_helper.rb +0 -40
- data/tests.sh +0 -11
data/test/parallelism_test.rb
CHANGED
@@ -1,35 +1,37 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
2
4
|
require 'forwardable'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def_delegators :@thread, :join, :wakeup, :status, :to_s
|
6
|
+
class FindOrCreateWorker
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@thread, :join, :wakeup, :status, :to_s
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def initialize(name, use_advisory_lock)
|
11
|
+
@name = name
|
12
|
+
@use_advisory_lock = use_advisory_lock
|
13
|
+
@thread = Thread.new { work_later }
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
16
|
+
def work_later
|
17
|
+
sleep
|
18
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
19
|
+
if @use_advisory_lock
|
20
|
+
Tag.with_advisory_lock(@name) { work }
|
21
|
+
else
|
22
|
+
work
|
23
23
|
end
|
24
24
|
end
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
27
|
+
def work
|
28
|
+
Tag.transaction do
|
29
|
+
Tag.where(name: @name).first_or_create
|
30
30
|
end
|
31
31
|
end
|
32
|
+
end
|
32
33
|
|
34
|
+
class ParallelismTest < GemTestCase
|
33
35
|
def run_workers
|
34
36
|
@names = @iterations.times.map { |iter| "iteration ##{iter}" }
|
35
37
|
@names.each do |name|
|
@@ -37,9 +39,7 @@ describe 'parallelism' do
|
|
37
39
|
FindOrCreateWorker.new(name, @use_advisory_lock)
|
38
40
|
end
|
39
41
|
# Wait for all the threads to get ready:
|
40
|
-
until workers.all? { |ea| ea.status == 'sleep' }
|
41
|
-
sleep(0.1)
|
42
|
-
end
|
42
|
+
sleep(0.1) until workers.all? { |ea| ea.status == 'sleep' }
|
43
43
|
# OK, GO!
|
44
44
|
workers.each(&:wakeup)
|
45
45
|
# Then wait for them to finish:
|
@@ -49,29 +49,27 @@ describe 'parallelism' do
|
|
49
49
|
ActiveRecord::Base.connection_pool.connection
|
50
50
|
end
|
51
51
|
|
52
|
-
|
52
|
+
setup do
|
53
53
|
ActiveRecord::Base.connection.reconnect!
|
54
54
|
@workers = 10
|
55
55
|
end
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
it 'creates multiple duplicate rows without advisory locks' do
|
60
|
-
skip if env_db == :sqlite
|
57
|
+
test 'creates multiple duplicate rows without advisory locks' do
|
58
|
+
skip if %i[sqlite3 jdbcsqlite3].include?(env_db)
|
61
59
|
@use_advisory_lock = false
|
62
60
|
@iterations = 1
|
63
61
|
run_workers
|
64
|
-
Tag.all.size
|
65
|
-
TagAudit.all.size
|
66
|
-
Label.all.size
|
62
|
+
assert_operator(Tag.all.size, :>, @iterations) # <- any duplicated rows will make me happy.
|
63
|
+
assert_operator(TagAudit.all.size, :>, @iterations) # <- any duplicated rows will make me happy.
|
64
|
+
assert_operator(Label.all.size, :>, @iterations) # <- any duplicated rows will make me happy.
|
67
65
|
end
|
68
66
|
|
69
|
-
|
67
|
+
test "doesn't create multiple duplicate rows with advisory locks" do
|
70
68
|
@use_advisory_lock = true
|
71
69
|
@iterations = 10
|
72
70
|
run_workers
|
73
|
-
Tag.all.size
|
74
|
-
TagAudit.all.size
|
75
|
-
Label.all.size
|
71
|
+
assert_equal(@iterations, Tag.all.size) # <- any duplicated rows will NOT make me happy.
|
72
|
+
assert_equal(@iterations, TagAudit.all.size) # <- any duplicated rows will NOT make me happy.
|
73
|
+
assert_equal(@iterations, Label.all.size) # <- any duplicated rows will NOT make me happy.
|
76
74
|
end
|
77
75
|
end
|
data/test/shared_test.rb
CHANGED
@@ -1,131 +1,134 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
class SharedTestWorker
|
9
|
-
def initialize(shared)
|
10
|
-
@shared = shared
|
3
|
+
require 'test_helper'
|
4
|
+
class SharedTestWorker
|
5
|
+
def initialize(shared)
|
6
|
+
@shared = shared
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
@locked = nil
|
9
|
+
@cleanup = false
|
10
|
+
@thread = Thread.new { work }
|
11
|
+
end
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
def locked?
|
14
|
+
sleep 0.01 while @locked.nil? && @thread.alive?
|
15
|
+
@locked
|
16
|
+
end
|
21
17
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
def cleanup!
|
19
|
+
@cleanup = true
|
20
|
+
@thread.join
|
21
|
+
raise if @thread.status.nil?
|
22
|
+
end
|
27
23
|
|
28
|
-
|
24
|
+
private
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
sleep 0.01 until @cleanup
|
35
|
-
end
|
36
|
-
@locked = false
|
26
|
+
def work
|
27
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
28
|
+
Tag.with_advisory_lock('test', timeout_seconds: 0, shared: @shared) do
|
29
|
+
@locked = true
|
37
30
|
sleep 0.01 until @cleanup
|
38
31
|
end
|
32
|
+
@locked = false
|
33
|
+
sleep 0.01 until @cleanup
|
39
34
|
end
|
40
35
|
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class SharedLocksTest < GemTestCase
|
39
|
+
def supported?
|
40
|
+
%i[trilogy mysql2 jdbcmysql].exclude?(env_db)
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
+
test 'does not allow two exclusive locks' do
|
43
44
|
one = SharedTestWorker.new(false)
|
44
|
-
one
|
45
|
+
assert_predicate(one, :locked?)
|
45
46
|
|
46
47
|
two = SharedTestWorker.new(false)
|
47
|
-
two.locked
|
48
|
+
refute(two.locked?)
|
48
49
|
|
49
50
|
one.cleanup!
|
50
51
|
two.cleanup!
|
51
52
|
end
|
53
|
+
end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
class NotSupportedEnvironmentTest < SharedLocksTest
|
56
|
+
setup do
|
57
|
+
skip if supported?
|
58
|
+
end
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
exception.message.must_include 'not supported'
|
60
|
+
test 'raises an error when attempting to use a shared lock' do
|
61
|
+
one = SharedTestWorker.new(true)
|
62
|
+
assert_nil(one.locked?)
|
63
|
+
|
64
|
+
exception = assert_raises(ArgumentError) do
|
65
|
+
one.cleanup!
|
65
66
|
end
|
67
|
+
|
68
|
+
assert_match(/#{Regexp.escape('not supported')}/, exception.message)
|
66
69
|
end
|
70
|
+
end
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
class SupportedEnvironmentTest < SharedLocksTest
|
73
|
+
setup do
|
74
|
+
skip unless supported?
|
75
|
+
end
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
|
77
|
+
test 'does allow two shared locks' do
|
78
|
+
one = SharedTestWorker.new(true)
|
79
|
+
assert_predicate(one, :locked?)
|
76
80
|
|
77
|
-
|
78
|
-
|
81
|
+
two = SharedTestWorker.new(true)
|
82
|
+
assert_predicate(two, :locked?)
|
79
83
|
|
80
|
-
|
81
|
-
|
82
|
-
|
84
|
+
one.cleanup!
|
85
|
+
two.cleanup!
|
86
|
+
end
|
83
87
|
|
84
|
-
|
85
|
-
|
86
|
-
|
88
|
+
test 'does not allow exclusive lock with shared lock' do
|
89
|
+
one = SharedTestWorker.new(true)
|
90
|
+
assert_predicate(one, :locked?)
|
87
91
|
|
88
|
-
|
89
|
-
|
92
|
+
two = SharedTestWorker.new(false)
|
93
|
+
refute(two.locked?)
|
90
94
|
|
91
|
-
|
92
|
-
|
95
|
+
three = SharedTestWorker.new(true)
|
96
|
+
assert_predicate(three, :locked?)
|
93
97
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
+
one.cleanup!
|
99
|
+
two.cleanup!
|
100
|
+
three.cleanup!
|
101
|
+
end
|
98
102
|
|
99
|
-
|
100
|
-
|
101
|
-
|
103
|
+
test 'does not allow shared lock with exclusive lock' do
|
104
|
+
one = SharedTestWorker.new(false)
|
105
|
+
assert_predicate(one, :locked?)
|
102
106
|
|
103
|
-
|
104
|
-
|
107
|
+
two = SharedTestWorker.new(true)
|
108
|
+
refute(two.locked?)
|
105
109
|
|
106
|
-
|
107
|
-
|
108
|
-
|
110
|
+
one.cleanup!
|
111
|
+
two.cleanup!
|
112
|
+
end
|
109
113
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
+
class PostgreSQLTest < SupportedEnvironmentTest
|
115
|
+
setup do
|
116
|
+
skip unless env_db == :postgresql
|
117
|
+
end
|
114
118
|
|
115
|
-
|
116
|
-
|
117
|
-
|
119
|
+
def pg_lock_modes
|
120
|
+
ActiveRecord::Base.connection.select_values("SELECT mode FROM pg_locks WHERE locktype = 'advisory';")
|
121
|
+
end
|
118
122
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
end
|
123
|
+
test 'allows shared lock to be upgraded to an exclusive lock' do
|
124
|
+
assert_empty(pg_lock_modes)
|
125
|
+
Tag.with_advisory_lock 'test', shared: true do
|
126
|
+
assert_equal(%w[ShareLock], pg_lock_modes)
|
127
|
+
Tag.with_advisory_lock 'test', shared: false do
|
128
|
+
assert_equal(%w[ShareLock ExclusiveLock], pg_lock_modes)
|
126
129
|
end
|
127
|
-
pg_lock_modes.must_equal %w[]
|
128
130
|
end
|
131
|
+
assert_empty(pg_lock_modes)
|
129
132
|
end
|
130
133
|
end
|
131
134
|
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'active_record'
|
5
|
+
require 'with_advisory_lock'
|
6
|
+
require 'tmpdir'
|
7
|
+
require 'securerandom'
|
8
|
+
begin
|
9
|
+
require 'activerecord-trilogy-adapter'
|
10
|
+
ActiveSupport.on_load(:active_record) do
|
11
|
+
require "trilogy_adapter/connection"
|
12
|
+
ActiveRecord::Base.public_send :extend, TrilogyAdapter::Connection
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
# do nothing
|
16
|
+
end
|
17
|
+
|
18
|
+
ActiveRecord::Base.configurations = {
|
19
|
+
default_env: {
|
20
|
+
url: ENV.fetch('DATABASE_URL', "sqlite3://#{Dir.tmpdir}/#{SecureRandom.hex}.sqlite3"),
|
21
|
+
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
ENV['WITH_ADVISORY_LOCK_PREFIX'] ||= SecureRandom.hex
|
26
|
+
|
27
|
+
ActiveRecord::Base.establish_connection
|
28
|
+
|
29
|
+
def env_db
|
30
|
+
@env_db ||= ActiveRecord::Base.connection_db_config.adapter.to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::Migration.verbose = false
|
34
|
+
|
35
|
+
require 'test_models'
|
36
|
+
require 'minitest'
|
37
|
+
require 'maxitest/autorun'
|
38
|
+
require 'mocha/minitest'
|
39
|
+
|
40
|
+
class GemTestCase < ActiveSupport::TestCase
|
41
|
+
setup do
|
42
|
+
ENV['FLOCK_DIR'] = Dir.mktmpdir
|
43
|
+
Tag.delete_all
|
44
|
+
TagAudit.delete_all
|
45
|
+
Label.delete_all
|
46
|
+
end
|
47
|
+
teardown do
|
48
|
+
FileUtils.remove_entry_secure ENV['FLOCK_DIR']
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
puts "Testing with #{env_db} database, ActiveRecord #{ActiveRecord.gem_version} and #{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} as #{RUBY_VERSION}"
|
data/test/test_models.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ActiveRecord::Schema.define(version: 0) do
|
4
|
+
create_table 'tags', force: true do |t|
|
5
|
+
t.string 'name'
|
4
6
|
end
|
5
|
-
create_table
|
6
|
-
t.string
|
7
|
+
create_table 'tag_audits', id: false, force: true do |t|
|
8
|
+
t.string 'tag_name'
|
7
9
|
end
|
8
|
-
create_table
|
9
|
-
t.string
|
10
|
+
create_table 'labels', id: false, force: true do |t|
|
11
|
+
t.string 'name'
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
data/test/thread_test.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
let(:lock_name) { 'testing 1,2,3' } # OMG COMMAS
|
3
|
+
require 'test_helper'
|
5
4
|
|
6
|
-
|
5
|
+
class SeparateThreadTest < GemTestCase
|
6
|
+
setup do
|
7
|
+
@lock_name = 'testing 1,2,3' # OMG COMMAS
|
7
8
|
@mutex = Mutex.new
|
8
9
|
@t1_acquired_lock = false
|
9
10
|
@t1_return_value = nil
|
10
11
|
|
11
12
|
@t1 = Thread.new do
|
12
13
|
ActiveRecord::Base.connection_pool.with_connection do
|
13
|
-
@t1_return_value = Label.with_advisory_lock(lock_name) do
|
14
|
+
@t1_return_value = Label.with_advisory_lock(@lock_name) do
|
14
15
|
@mutex.synchronize { @t1_acquired_lock = true }
|
15
16
|
sleep
|
16
17
|
't1 finished'
|
@@ -19,42 +20,42 @@ describe 'separate thread tests' do
|
|
19
20
|
end
|
20
21
|
|
21
22
|
# Wait for the thread to acquire the lock:
|
22
|
-
until @mutex.synchronize { @t1_acquired_lock }
|
23
|
-
sleep(0.1)
|
24
|
-
end
|
23
|
+
sleep(0.1) until @mutex.synchronize { @t1_acquired_lock }
|
25
24
|
ActiveRecord::Base.connection.reconnect!
|
26
25
|
end
|
27
26
|
|
28
|
-
|
27
|
+
teardown do
|
29
28
|
@t1.wakeup if @t1.status == 'sleep'
|
30
29
|
@t1.join
|
31
30
|
end
|
32
31
|
|
33
|
-
|
34
|
-
response = Label.with_advisory_lock(lock_name, 0) do
|
35
|
-
|
32
|
+
test '#with_advisory_lock with a 0 timeout returns false immediately' do
|
33
|
+
response = Label.with_advisory_lock(@lock_name, 0) do
|
34
|
+
raise 'should not be yielded to'
|
36
35
|
end
|
37
|
-
response
|
36
|
+
assert_not(response)
|
38
37
|
end
|
39
38
|
|
40
|
-
|
41
|
-
@t1_acquired_lock
|
39
|
+
test '#with_advisory_lock yields to the provided block' do
|
40
|
+
assert(@t1_acquired_lock)
|
42
41
|
end
|
43
42
|
|
44
|
-
|
45
|
-
Tag.advisory_lock_exists?(lock_name)
|
43
|
+
test '#advisory_lock_exists? returns true when another thread has the lock' do
|
44
|
+
assert(Tag.advisory_lock_exists?(@lock_name))
|
46
45
|
end
|
47
46
|
|
48
|
-
|
47
|
+
test 'can re-establish the lock after the other thread releases it' do
|
49
48
|
@t1.wakeup
|
50
49
|
@t1.join
|
51
|
-
|
50
|
+
assert_equal('t1 finished', @t1_return_value)
|
52
51
|
|
53
52
|
# We should now be able to acquire the lock immediately:
|
54
53
|
reacquired = false
|
55
|
-
Label.with_advisory_lock(lock_name, 0) do
|
54
|
+
lock_result = Label.with_advisory_lock(@lock_name, 0) do
|
56
55
|
reacquired = true
|
57
|
-
end
|
58
|
-
|
56
|
+
end
|
57
|
+
|
58
|
+
assert(lock_result)
|
59
|
+
assert(reacquired)
|
59
60
|
end
|
60
61
|
end
|
data/test/transaction_test.rb
CHANGED
@@ -1,70 +1,68 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TransactionScopingTest < GemTestCase
|
4
6
|
def supported?
|
5
|
-
env_db
|
7
|
+
%i[postgresql jdbcpostgresql].include?(env_db)
|
6
8
|
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
skip if supported?
|
11
|
-
end
|
10
|
+
test 'raises an error when attempting to use transaction level locks if not supported' do
|
11
|
+
skip if supported?
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
}.must_raise ArgumentError
|
20
|
-
exception.message.must_include 'not supported'
|
13
|
+
Tag.transaction do
|
14
|
+
exception = assert_raises(ArgumentError) do
|
15
|
+
Tag.with_advisory_lock 'test', transaction: true do
|
16
|
+
raise 'should not get here'
|
17
|
+
end
|
21
18
|
end
|
19
|
+
|
20
|
+
assert_match(/#{Regexp.escape('not supported')}/, exception.message)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
|
-
|
26
|
-
|
24
|
+
class PostgresqlTest < TransactionScopingTest
|
25
|
+
setup do
|
27
26
|
skip unless env_db == :postgresql
|
27
|
+
@pg_lock_count = lambda do
|
28
|
+
ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM pg_locks WHERE locktype = 'advisory';").to_i
|
29
|
+
end
|
28
30
|
end
|
29
31
|
|
30
|
-
|
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
|
32
|
+
test 'session locks release after the block executes' do
|
35
33
|
Tag.transaction do
|
36
|
-
pg_lock_count.
|
34
|
+
assert_equal(0, @pg_lock_count.call)
|
37
35
|
Tag.with_advisory_lock 'test' do
|
38
|
-
pg_lock_count.
|
36
|
+
assert_equal(1, @pg_lock_count.call)
|
39
37
|
end
|
40
|
-
pg_lock_count.
|
38
|
+
assert_equal(0, @pg_lock_count.call)
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
44
|
-
|
42
|
+
test 'session locks release when transaction fails inside block' do
|
45
43
|
Tag.transaction do
|
46
|
-
pg_lock_count.
|
44
|
+
assert_equal(0, @pg_lock_count.call)
|
47
45
|
|
48
|
-
exception =
|
46
|
+
exception = assert_raises(ActiveRecord::StatementInvalid) do
|
49
47
|
Tag.with_advisory_lock 'test' do
|
50
48
|
Tag.connection.execute 'SELECT 1/0;'
|
51
49
|
end
|
52
|
-
|
53
|
-
exception.message.must_include 'division by zero'
|
50
|
+
end
|
54
51
|
|
55
|
-
|
52
|
+
assert_match(/#{Regexp.escape('division by zero')}/, exception.message)
|
53
|
+
assert_equal(0, @pg_lock_count.call)
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
59
|
-
|
57
|
+
test 'transaction level locks hold until the transaction completes' do
|
60
58
|
Tag.transaction do
|
61
|
-
pg_lock_count.
|
59
|
+
assert_equal(0, @pg_lock_count.call)
|
62
60
|
Tag.with_advisory_lock 'test', transaction: true do
|
63
|
-
pg_lock_count.
|
61
|
+
assert_equal(1, @pg_lock_count.call)
|
64
62
|
end
|
65
|
-
pg_lock_count.
|
63
|
+
assert_equal(1, @pg_lock_count.call)
|
66
64
|
end
|
67
|
-
pg_lock_count.
|
65
|
+
assert_equal(0, @pg_lock_count.call)
|
68
66
|
end
|
69
67
|
end
|
70
68
|
end
|
data/with_advisory_lock.gemspec
CHANGED
@@ -1,29 +1,30 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require 'with_advisory_lock/version'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
gem.version = WithAdvisoryLock::VERSION
|
8
|
-
gem.authors = ['Matthew McEachen']
|
9
|
-
gem.email = %w(matthew+github@mceachen.org)
|
10
|
-
gem.homepage = 'https://github.com/mceachen/with_advisory_lock'
|
11
|
-
gem.summary = %q{Advisory locking for ActiveRecord}
|
12
|
-
gem.description = %q{Advisory locking for ActiveRecord}
|
13
|
-
gem.license = 'MIT'
|
3
|
+
require 'English'
|
4
|
+
require_relative 'lib/with_advisory_lock/version'
|
14
5
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'with_advisory_lock'
|
8
|
+
spec.version = WithAdvisoryLock::VERSION
|
9
|
+
spec.authors = ['Matthew McEachen', 'Abdelkader Boudih']
|
10
|
+
spec.email = %w[matthew+github@mceachen.org terminale@gmail.com]
|
11
|
+
spec.homepage = 'https://github.com/ClosureTree/with_advisory_lock'
|
12
|
+
spec.summary = 'Advisory locking for ActiveRecord'
|
13
|
+
spec.description = 'Advisory locking for ActiveRecord'
|
14
|
+
spec.license = 'MIT'
|
19
15
|
|
20
|
-
|
16
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
|
+
spec.test_files = spec.files.grep(%r{^test/})
|
18
|
+
spec.require_paths = %w[lib]
|
19
|
+
spec.metadata = { "rubyspecs_mfa_required" => "true" }
|
20
|
+
spec.required_ruby_version = '>= 2.7.0'
|
21
|
+
spec.metadata["yard.run"] = "yri"
|
21
22
|
|
23
|
+
spec.add_runtime_dependency 'activerecord', '>= 6.1'
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
gem.add_development_dependency 'appraisal'
|
25
|
+
spec.add_development_dependency 'appraisal'
|
26
|
+
spec.add_development_dependency 'maxitest'
|
27
|
+
spec.add_development_dependency 'minitest-reporters'
|
28
|
+
spec.add_development_dependency 'mocha'
|
29
|
+
spec.add_development_dependency 'yard'
|
29
30
|
end
|