with_advisory_lock 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aeb99a0ddec77a51eeb59a41a26411a25390c65b
4
- data.tar.gz: 3ee6f67835cbd6f3fe515f41b8134478ef2102ce
3
+ metadata.gz: d83c8384aa5b5b063851f1bcef61584c552c6845
4
+ data.tar.gz: 2ff00f6afba93642d8d2bcb0c6ae639dec2ff913
5
5
  SHA512:
6
- metadata.gz: 58919a4b30f793b056f8c9f2af7f06f40673f5e9e6ef68213760f49b348f9044087ab16a1b398619e7c47bcba14db7eecadaf50a6f5652bfa5e36bb0ef1d9fc7
7
- data.tar.gz: a819c9b4d0c2607505b90f9dbb8f981c041c3608ae69a63e7dac621d105479be268b5d425f1ec946c225b36175d595ed2115782a9a239cd3ad671c6e3b25d665
6
+ metadata.gz: 2b1529ea567b8f99008590582827e0b7b4a5c03af8fa779253f68c40aac1c9eaaa0cea4537f1007027c3148422af3ca59579416443443328aa27775560deda22
7
+ data.tar.gz: 06f6b2a54dc18806663041c0954a5d222ed533023e8e01a5c61a21e078836cc9e4fd00a822b35f825da1e144cdedeaf8ee396e2224ff83db413ae792288ad83a
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  *.gem
2
2
  *.rbc
3
+ *.history
3
4
  *.idea
4
5
  .bundle
5
6
  .config
@@ -1,21 +1,21 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - jruby-19mode
5
- - 2.1.2
6
- - 1.9.3
7
- # TODO - rbx-2
4
+ - 2.4.0
5
+ - 2.3.3
6
+ - 2.2.6
8
7
 
9
8
  gemfile:
10
- - gemfiles/activerecord_3.2.gemfile
11
- - gemfiles/activerecord_4.0.gemfile
12
- - gemfiles/activerecord_4.1.gemfile
13
- - gemfiles/activerecord_edge.gemfile
9
+ - gemfiles/activerecord_5.0.gemfile
10
+ - gemfiles/activerecord_4.2.gemfile
14
11
 
15
12
  env:
16
- - DB=sqlite
17
- - DB=mysql
18
13
  - DB=postgresql
14
+ - DB=mysql
15
+ - DB=sqlite
16
+
17
+ before_install:
18
+ - gem install bundler
19
19
 
20
20
  script: WITH_ADVISORY_LOCK_PREFIX=$TRAVIS_JOB_ID bundle exec rake --trace
21
21
 
data/Appraisals CHANGED
@@ -1,16 +1,7 @@
1
- appraise "activerecord-3.2" do
2
- gem 'activerecord', '~> 3.2.0'
1
+ appraise "activerecord-4.2" do
2
+ gem "activerecord", "~> 4.2.0"
3
3
  end
4
4
 
5
- appraise "activerecord-4.0" do
6
- gem "activerecord", "~> 4.0.0"
7
- end
8
-
9
- appraise "activerecord-4.1" do
10
- gem "activerecord", "~> 4.1.0"
11
- end
12
-
13
- appraise "activerecord-edge" do
14
- gem "activerecord", github: "rails/rails"
15
- gem 'arel', github: 'rails/arel'
5
+ appraise "activerecord-5.0" do
6
+ gem "activerecord", "~> 5.0.0"
16
7
  end
data/Gemfile CHANGED
@@ -3,8 +3,8 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  platforms :ruby do
6
- gem 'mysql2'
7
- gem 'pg'
6
+ gem 'mysql2', '~> 0.3.10' # Rails 3.2 requires 0.3.x
7
+ gem 'pg', '< 0.19' # 0.19 requires Ruby 2.0+
8
8
  gem 'sqlite3'
9
9
  end
10
10
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # with_advisory_lock
2
2
 
3
- Adds advisory locking (mutexes) to ActiveRecord 3.2, 4.0 and 4.1 when used with
3
+ Adds advisory locking (mutexes) to ActiveRecord 4.2 and 5.0, with ruby 2.4, 2.3 or 2.2, when used with
4
4
  [MySQL](http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock)
5
5
  or [PostgreSQL](http://www.postgresql.org/docs/9.3/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS).
6
6
  SQLite resorts to file locking.
@@ -34,8 +34,8 @@ end
34
34
 
35
35
  ### Lock wait timeouts
36
36
 
37
- The second parameter for ```with_advisory_lock``` is ```timeout_seconds```, and defaults to ```nil```,
38
- which means wait indefinitely for the lock.
37
+ ```with_advisory_lock``` takes an options hash as the second parameter.
38
+ The ```timeout_seconds``` option defaults to ```nil```, which means wait indefinitely for the lock.
39
39
 
40
40
  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.
@@ -43,6 +43,23 @@ will be yielded to. If the lock is currently being held, the block will not be c
43
43
  Note that if a non-nil value is provided for `timeout_seconds`, the block will not be invoked if
44
44
  the lock cannot be acquired within that time-frame.
45
45
 
46
+ For backwards compatability, the timeout value can be specified directly as the second parameter.
47
+
48
+ ### Shared locks
49
+
50
+ The ```shared``` option defaults to ```false``` which means an exclusive lock will be obtained.
51
+ Setting ```shared``` to ```true``` will allow locks to be obtained by multiple actors
52
+ as long as they are all shared locks.
53
+
54
+ Note: MySQL does not support shared locks.
55
+
56
+ ### Transaction-level locks
57
+
58
+ PostgreSQL supports transaction-level locks which remain held until the transaction completes.
59
+ You can enable this by setting the ```transaction``` option to ```true```.
60
+
61
+ Note: transaction-level locks will not be reflected by `.current_advisory_lock` when the block has returned.
62
+
46
63
  ### Return values
47
64
 
48
65
  The return value of `with_advisory_lock_result` is a `WithAdvisoryLock::Result` instance,
@@ -117,6 +134,10 @@ you will be releasing the parent lock (!!!). A ```NestedAdvisoryLockError```will
117
134
  in this case. If you ask for the same lock name, ```with_advisory_lock``` won't ask for the
118
135
  lock again, and the block given will be yielded to.
119
136
 
137
+ ### Is clustered MySQL supported?
138
+
139
+ [No.](https://github.com/mceachen/with_advisory_lock/issues/16)
140
+
120
141
  ### There are many ```lock-*``` files in my project directory after test runs
121
142
 
122
143
  This is expected if you aren't using MySQL or Postgresql for your tests.
@@ -139,6 +160,12 @@ end
139
160
 
140
161
  ## Changelog
141
162
 
163
+ ### 3.1.0
164
+
165
+ * [Jason Weathered](https://github.com/jasoncodes) Added new shared and transaction-level lock options ([Pull request 21](https://github.com/mceachen/with_advisory_lock/pull/21)). Thanks!
166
+ * Added ActiveRecord 5.0 to build matrix. Dropped 3.2, 4.0, and 4.1 (which no longer get security updates: http://rubyonrails.org/security/)
167
+ * Replaced ruby 1.9 and 2.0 (both EOL) with ruby 2.2 and 2.3 (see https://www.ruby-lang.org/en/downloads/)
168
+
142
169
  ### 3.0.0
143
170
 
144
171
  * Added jruby/PostgreSQL support for Rails 4.x
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 3.2.0"
5
+ gem "activerecord", "~> 4.2.0"
6
6
 
7
7
  platforms :ruby do
8
8
  gem "mysql2"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 4.0.0"
5
+ gem "activerecord", "~> 5.0.0"
6
6
 
7
7
  platforms :ruby do
8
8
  gem "mysql2"
@@ -16,19 +16,30 @@ module WithAdvisoryLock
16
16
 
17
17
  FAILED_TO_LOCK = Result.new(false)
18
18
 
19
+ LockStackItem = Struct.new(:name, :shared)
20
+
19
21
  class Base
20
- attr_reader :connection, :lock_name, :timeout_seconds
22
+ attr_reader :connection, :lock_name, :timeout_seconds, :shared, :transaction
23
+
24
+ def initialize(connection, lock_name, options)
25
+ options = {timeout_seconds: options} unless options.respond_to?(:fetch)
26
+ options.assert_valid_keys :timeout_seconds, :shared, :transaction
21
27
 
22
- def initialize(connection, lock_name, timeout_seconds)
23
28
  @connection = connection
24
29
  @lock_name = lock_name
25
- @timeout_seconds = timeout_seconds
30
+ @timeout_seconds = options.fetch(:timeout_seconds, nil)
31
+ @shared = options.fetch(:shared, false)
32
+ @transaction = options.fetch(:transaction, false)
26
33
  end
27
34
 
28
35
  def lock_str
29
36
  @lock_str ||= "#{ENV['WITH_ADVISORY_LOCK_PREFIX'].to_s}#{lock_name.to_s}"
30
37
  end
31
38
 
39
+ def lock_stack_item
40
+ @lock_stack_item ||= LockStackItem.new(lock_str, shared)
41
+ end
42
+
32
43
  def self.lock_stack
33
44
  # access doesn't need to be synchronized as it is only accessed by the current thread.
34
45
  Thread.current[:with_advisory_lock_stack] ||= []
@@ -36,7 +47,7 @@ module WithAdvisoryLock
36
47
  delegate :lock_stack, to: 'self.class'
37
48
 
38
49
  def already_locked?
39
- lock_stack.include? lock_str
50
+ lock_stack.include? lock_stack_item
40
51
  end
41
52
 
42
53
  def with_advisory_lock_if_needed(&block)
@@ -73,7 +84,7 @@ module WithAdvisoryLock
73
84
  def yield_with_lock
74
85
  if try_lock
75
86
  begin
76
- lock_stack.push(lock_str)
87
+ lock_stack.push(lock_stack_item)
77
88
  result = block_given? ? yield : nil
78
89
  Result.new(true, result)
79
90
  ensure
@@ -6,13 +6,13 @@ module WithAdvisoryLock
6
6
  delegate :with_advisory_lock, :advisory_lock_exists?, to: 'self.class'
7
7
 
8
8
  module ClassMethods
9
- def with_advisory_lock(lock_name, timeout_seconds=nil, &block)
10
- result = with_advisory_lock_result(lock_name, timeout_seconds, &block)
9
+ def with_advisory_lock(lock_name, options={}, &block)
10
+ result = with_advisory_lock_result(lock_name, options, &block)
11
11
  result.lock_was_acquired? ? result.result : false
12
12
  end
13
13
 
14
- def with_advisory_lock_result(lock_name, timeout_seconds=nil, &block)
15
- impl = impl_class.new(connection, lock_name, timeout_seconds)
14
+ def with_advisory_lock_result(lock_name, options={}, &block)
15
+ impl = impl_class.new(connection, lock_name, options)
16
16
  impl.with_advisory_lock_if_needed(&block)
17
17
  end
18
18
 
@@ -22,7 +22,8 @@ module WithAdvisoryLock
22
22
  end
23
23
 
24
24
  def current_advisory_lock
25
- WithAdvisoryLock::Base.lock_stack.first
25
+ lock_stack_key = WithAdvisoryLock::Base.lock_stack.first
26
+ lock_stack_key && lock_stack_key[0]
26
27
  end
27
28
 
28
29
  private
@@ -20,7 +20,10 @@ module WithAdvisoryLock
20
20
  end
21
21
 
22
22
  def try_lock
23
- 0 == file_io.flock(File::LOCK_EX|File::LOCK_NB)
23
+ if transaction
24
+ raise ArgumentError, 'transaction level locks are not supported on SQLite'
25
+ end
26
+ 0 == file_io.flock((shared ? File::LOCK_SH : File::LOCK_EX) | File::LOCK_NB)
24
27
  end
25
28
 
26
29
  def release_lock
@@ -7,6 +7,12 @@ module WithAdvisoryLock
7
7
  "MySQL doesn't support nested Advisory Locks",
8
8
  lock_stack.dup)
9
9
  end
10
+ if shared
11
+ raise ArgumentError, 'shared locks are not supported on MySQL'
12
+ end
13
+ if transaction
14
+ raise ArgumentError, 'transaction level locks are not supported on MySQL'
15
+ end
10
16
  execute_successful?("GET_LOCK(#{quoted_lock_str}, 0)")
11
17
  end
12
18
 
@@ -21,7 +27,7 @@ module WithAdvisoryLock
21
27
 
22
28
  # MySQL doesn't support nested locks:
23
29
  def already_locked?
24
- lock_stack.last == lock_str
30
+ lock_stack.last == lock_stack_item
25
31
  end
26
32
 
27
33
  # MySQL wants a string as the lock key.
@@ -2,11 +2,22 @@ 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
- execute_successful?('pg_try_advisory_lock')
5
+ pg_function = "pg_try_advisory#{transaction ? '_xact' : ''}_lock#{shared ? '_shared' : ''}"
6
+ execute_successful?(pg_function)
6
7
  end
7
8
 
8
9
  def release_lock
9
- execute_successful?('pg_advisory_unlock')
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
10
21
  end
11
22
 
12
23
  def execute_successful?(pg_function)
@@ -1,3 +1,3 @@
1
1
  module WithAdvisoryLock
2
- VERSION = Gem::Version.new('3.0.0')
2
+ VERSION = Gem::Version.new('3.1.0')
3
3
  end
@@ -15,12 +15,12 @@ describe "lock nesting" do
15
15
  impl = WithAdvisoryLock::Base.new(nil, nil, nil)
16
16
  impl.lock_stack.must_be_empty
17
17
  Tag.with_advisory_lock("first") do
18
- impl.lock_stack.must_equal %w(first)
18
+ impl.lock_stack.map(&:name).must_equal %w(first)
19
19
  # Even MySQL should be OK with this:
20
20
  Tag.with_advisory_lock("first") do
21
- impl.lock_stack.must_equal %w(first)
21
+ impl.lock_stack.map(&:name).must_equal %w(first)
22
22
  end
23
- impl.lock_stack.must_equal %w(first)
23
+ impl.lock_stack.map(&:name).must_equal %w(first)
24
24
  end
25
25
  impl.lock_stack.must_be_empty
26
26
  end
@@ -33,7 +33,7 @@ describe "lock nesting" do
33
33
  end
34
34
  end
35
35
  }.must_raise WithAdvisoryLock::NestedAdvisoryLockError
36
- exc.lock_stack.must_equal %w(first)
36
+ exc.lock_stack.map(&:name).must_equal %w(first)
37
37
  end
38
38
 
39
39
  it "supports nested advisory locks with !MySQL" do
@@ -41,19 +41,19 @@ describe "lock nesting" do
41
41
  impl = WithAdvisoryLock::Base.new(nil, nil, nil)
42
42
  impl.lock_stack.must_be_empty
43
43
  Tag.with_advisory_lock("first") do
44
- impl.lock_stack.must_equal %w(first)
44
+ impl.lock_stack.map(&:name).must_equal %w(first)
45
45
  Tag.with_advisory_lock("second") do
46
- impl.lock_stack.must_equal %w(first second)
46
+ impl.lock_stack.map(&:name).must_equal %w(first second)
47
47
  Tag.with_advisory_lock("first") do
48
48
  # Shouldn't ask for another lock:
49
- impl.lock_stack.must_equal %w(first second)
49
+ impl.lock_stack.map(&:name).must_equal %w(first second)
50
50
  Tag.with_advisory_lock("second") do
51
51
  # Shouldn't ask for another lock:
52
- impl.lock_stack.must_equal %w(first second)
52
+ impl.lock_stack.map(&:name).must_equal %w(first second)
53
53
  end
54
54
  end
55
55
  end
56
- impl.lock_stack.must_equal %w(first)
56
+ impl.lock_stack.map(&:name).must_equal %w(first)
57
57
  end
58
58
  impl.lock_stack.must_be_empty
59
59
  end
@@ -0,0 +1,64 @@
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_equal 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_equal 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_equal 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_equal 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
@@ -4,7 +4,7 @@ require 'forwardable'
4
4
  describe 'parallelism' do
5
5
  class FindOrCreateWorker
6
6
  extend Forwardable
7
- def_delegators :@thread, :join, :wakeup, :join, :status, :to_s
7
+ def_delegators :@thread, :join, :wakeup, :status, :to_s
8
8
 
9
9
  def initialize(name, use_advisory_lock)
10
10
  @name = name
@@ -72,4 +72,3 @@ describe 'parallelism' do
72
72
  Label.all.size.must_equal @iterations # <- any duplicated rows will NOT make me happy.
73
73
  end
74
74
  end
75
-
@@ -0,0 +1,131 @@
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_equal 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
@@ -0,0 +1,70 @@
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
data/tests.sh CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash -e
2
2
  export DB
3
3
 
4
- for RUBY in 2.1.2 jruby-1.7.13 ; do
4
+ for RUBY in 2.4.0 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)"
metadata CHANGED
@@ -1,125 +1,125 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: with_advisory_lock
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.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-08-03 00:00:00.000000000 Z
11
+ date: 2017-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
+ name: activerecord
14
15
  requirement: !ruby/object:Gem::Requirement
15
16
  requirements:
16
- - - '>='
17
+ - - ">="
17
18
  - !ruby/object:Gem::Version
18
19
  version: '3.2'
19
- name: activerecord
20
- prerelease: false
21
20
  type: :runtime
21
+ prerelease: false
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: thread_safe
28
29
  requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
- - - '>='
31
+ - - ">="
31
32
  - !ruby/object:Gem::Version
32
33
  version: '0'
33
- name: thread_safe
34
- prerelease: false
35
34
  type: :runtime
35
+ prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
+ name: yard
42
43
  requirement: !ruby/object:Gem::Requirement
43
44
  requirements:
44
- - - '>='
45
+ - - ">="
45
46
  - !ruby/object:Gem::Version
46
47
  version: '0'
47
- name: yard
48
- prerelease: false
49
48
  type: :development
49
+ prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
+ name: minitest
56
57
  requirement: !ruby/object:Gem::Requirement
57
58
  requirements:
58
- - - '>='
59
+ - - ">="
59
60
  - !ruby/object:Gem::Version
60
61
  version: '0'
61
- name: minitest
62
- prerelease: false
63
62
  type: :development
63
+ prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
+ name: minitest-great_expectations
70
71
  requirement: !ruby/object:Gem::Requirement
71
72
  requirements:
72
- - - '>='
73
+ - - ">="
73
74
  - !ruby/object:Gem::Version
74
75
  version: '0'
75
- name: minitest-great_expectations
76
- prerelease: false
77
76
  type: :development
77
+ prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
+ name: minitest-reporters
84
85
  requirement: !ruby/object:Gem::Requirement
85
86
  requirements:
86
- - - '>='
87
+ - - ">="
87
88
  - !ruby/object:Gem::Version
88
89
  version: '0'
89
- name: minitest-reporters
90
- prerelease: false
91
90
  type: :development
91
+ prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
+ name: mocha
98
99
  requirement: !ruby/object:Gem::Requirement
99
100
  requirements:
100
- - - '>='
101
+ - - ">="
101
102
  - !ruby/object:Gem::Version
102
103
  version: '0'
103
- name: mocha
104
- prerelease: false
105
104
  type: :development
105
+ prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
+ name: appraisal
112
113
  requirement: !ruby/object:Gem::Requirement
113
114
  requirements:
114
- - - '>='
115
+ - - ">="
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0'
117
- name: appraisal
118
- prerelease: false
119
118
  type: :development
119
+ prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - '>='
122
+ - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  description: Advisory locking for ActiveRecord
@@ -129,17 +129,15 @@ executables: []
129
129
  extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
- - .gitignore
133
- - .travis.yml
132
+ - ".gitignore"
133
+ - ".travis.yml"
134
134
  - Appraisals
135
135
  - Gemfile
136
136
  - LICENSE.txt
137
137
  - README.md
138
138
  - Rakefile
139
- - gemfiles/activerecord_3.2.gemfile
140
- - gemfiles/activerecord_4.0.gemfile
141
- - gemfiles/activerecord_4.1.gemfile
142
- - gemfiles/activerecord_edge.gemfile
139
+ - gemfiles/activerecord_4.2.gemfile
140
+ - gemfiles/activerecord_5.0.gemfile
143
141
  - lib/with_advisory_lock.rb
144
142
  - lib/with_advisory_lock/base.rb
145
143
  - lib/with_advisory_lock/concern.rb
@@ -154,33 +152,36 @@ files:
154
152
  - test/lock_test.rb
155
153
  - test/minitest_helper.rb
156
154
  - test/nesting_test.rb
155
+ - test/options_test.rb
157
156
  - test/parallelism_test.rb
157
+ - test/shared_test.rb
158
158
  - test/test_models.rb
159
159
  - test/thread_test.rb
160
+ - test/transaction_test.rb
160
161
  - tests.sh
161
162
  - with_advisory_lock.gemspec
162
163
  homepage: https://github.com/mceachen/with_advisory_lock
163
164
  licenses:
164
165
  - MIT
165
166
  metadata: {}
166
- post_install_message:
167
+ post_install_message:
167
168
  rdoc_options: []
168
169
  require_paths:
169
170
  - lib
170
171
  required_ruby_version: !ruby/object:Gem::Requirement
171
172
  requirements:
172
- - - '>='
173
+ - - ">="
173
174
  - !ruby/object:Gem::Version
174
175
  version: '0'
175
176
  required_rubygems_version: !ruby/object:Gem::Requirement
176
177
  requirements:
177
- - - '>='
178
+ - - ">="
178
179
  - !ruby/object:Gem::Version
179
180
  version: '0'
180
181
  requirements: []
181
- rubyforge_project:
182
- rubygems_version: 2.1.9
183
- signing_key:
182
+ rubyforge_project:
183
+ rubygems_version: 2.5.1
184
+ signing_key:
184
185
  specification_version: 4
185
186
  summary: Advisory locking for ActiveRecord
186
187
  test_files:
@@ -189,7 +190,9 @@ test_files:
189
190
  - test/lock_test.rb
190
191
  - test/minitest_helper.rb
191
192
  - test/nesting_test.rb
193
+ - test/options_test.rb
192
194
  - test/parallelism_test.rb
195
+ - test/shared_test.rb
193
196
  - test/test_models.rb
194
197
  - test/thread_test.rb
195
- has_rdoc:
198
+ - test/transaction_test.rb
@@ -1,19 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "~> 4.1.0"
6
-
7
- platforms :ruby do
8
- gem "mysql2"
9
- gem "pg"
10
- gem "sqlite3"
11
- end
12
-
13
- platforms :jruby do
14
- gem "activerecord-jdbcmysql-adapter"
15
- gem "activerecord-jdbcpostgresql-adapter"
16
- gem "activerecord-jdbcsqlite3-adapter"
17
- end
18
-
19
- gemspec :path => "../"
@@ -1,20 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", :github => "rails/rails"
6
- gem "arel", :github => "rails/arel"
7
-
8
- platforms :ruby do
9
- gem "mysql2"
10
- gem "pg"
11
- gem "sqlite3"
12
- end
13
-
14
- platforms :jruby do
15
- gem "activerecord-jdbcmysql-adapter"
16
- gem "activerecord-jdbcpostgresql-adapter"
17
- gem "activerecord-jdbcsqlite3-adapter"
18
- end
19
-
20
- gemspec :path => "../"