with_advisory_lock 0.0.1 → 0.0.2

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.
data/README.md CHANGED
@@ -66,6 +66,15 @@ The return value of ```with_advisory_lock``` will be the result of the yielded b
66
66
  if the lock was able to be acquired and the block yielded, or ```false```, if you provided
67
67
  a timeout_seconds value and the lock was not able to be acquired in time.
68
68
 
69
+ ### Gotchas
70
+
71
+ **MySQL doesn't support nesting advisory locks.** If you ask for another advisory lock within
72
+ a ```with_advisory_lock``` block, you will be releasing the parent lock.
73
+
74
+ An warning message will be emitted to the rails logger in this case, because you
75
+ probably didn't mean to lose the first lock. (Raising an exception would be safer. I'm open to
76
+ suggestions on how to handle this dangerous case).
77
+
69
78
  ## Installation
70
79
 
71
80
  Add this line to your application's Gemfile:
@@ -80,6 +89,11 @@ And then execute:
80
89
 
81
90
  ## Changelog
82
91
 
92
+ ### 0.0.2
93
+
94
+ * Added warning log message for nested MySQL lock calls
95
+ * Randomized lock wait time, which can help ameliorate lock contention
96
+
83
97
  ### 0.0.1
84
98
 
85
99
  * First whack
@@ -22,7 +22,9 @@ module WithAdvisoryLock
22
22
  release_lock
23
23
  end
24
24
  else
25
- sleep(0.1)
25
+ # sleep between 1/20 and ~1/5 of a second.
26
+ # Randomizing sleep time may help reduce contention.
27
+ sleep(rand * 0.15 + 0.05)
26
28
  end
27
29
  end
28
30
  false # failed to get lock in time.
@@ -18,16 +18,34 @@ module WithAdvisoryLock
18
18
  end
19
19
 
20
20
  module ClassMethods
21
+
21
22
  def with_advisory_lock(lock_name, timeout_seconds=nil, &block)
22
- case (connection.adapter_name.downcase)
23
+ lock_stack = Thread.current[:with_advisory_lock_stack] ||= []
24
+ impl = case (connection.adapter_name.downcase)
23
25
  when "postgresql"
24
26
  WithAdvisoryLock::PostgreSQL
25
27
  when "mysql", "mysql2"
28
+ unless lock_stack.empty?
29
+ wal_log("with_advisory_lock: MySQL doesn't support nested advisory locks, and will now release lock '#{lock_stack.last}'")
30
+ end
26
31
  WithAdvisoryLock::MySQL
27
32
  else
28
33
  WithAdvisoryLock::Flock
29
- end.new(connection, lock_name, timeout_seconds).with_advisory_lock(&block)
34
+ end
35
+ lock_stack.push(lock_name)
36
+ impl.new(connection, lock_name, timeout_seconds).with_advisory_lock(&block)
37
+ ensure
38
+ lock_stack.pop
39
+ end
40
+
41
+ def wal_log(msg)
42
+ if respond_to?(:logger) && logger
43
+ logger.warn(msg)
44
+ else
45
+ $stderr.puts(msg)
46
+ end
30
47
  end
48
+ private :wal_log
31
49
  end
32
50
  end
33
51
  end
@@ -1,3 +1,3 @@
1
1
  module WithAdvisoryLock
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,11 @@
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 Tag.new.respond_to?(:with_advisory_lock)
10
+ end
11
+ end
@@ -12,6 +12,7 @@ ActiveRecord::Migration.verbose = false
12
12
  require 'test_models'
13
13
  require 'minitest/autorun'
14
14
  require 'minitest/great_expectations'
15
+ require 'mocha/setup'
15
16
 
16
17
  Thread.abort_on_exception = true
17
18
 
@@ -0,0 +1,11 @@
1
+ require 'minitest_helper'
2
+
3
+ describe "lock nesting" do
4
+ it "warns about MySQL releasing advisory locks" do
5
+ Tag.expects(:wal_log)
6
+ Tag.with_advisory_lock("first") do
7
+ Tag.with_advisory_lock("second") do
8
+ end
9
+ end
10
+ end if ENV['DB'] == 'mysql'
11
+ end
@@ -1,14 +1,6 @@
1
1
  require 'minitest_helper'
2
2
 
3
- describe "with_advisory_lock" 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 Tag.new.respond_to?(:with_advisory_lock)
10
- end
11
-
3
+ describe "parallelism" do
12
4
  def find_or_create_at_even_second(run_at, with_advisory_lock)
13
5
  sleep(run_at - Time.now.to_f)
14
6
  ActiveRecord::Base.connection.reconnect!
@@ -36,7 +28,7 @@ describe "with_advisory_lock" do
36
28
 
37
29
  before :each do
38
30
  @iterations = 5
39
- @workers = 7
31
+ @workers = 10
40
32
  end
41
33
 
42
34
  it "parallel threads create multiple duplicate rows" do
@@ -23,6 +23,7 @@ Gem::Specification.new do |gem|
23
23
  gem.add_development_dependency 'yard'
24
24
  gem.add_development_dependency 'minitest'
25
25
  gem.add_development_dependency 'minitest-great_expectations'
26
+ gem.add_development_dependency 'mocha'
26
27
  gem.add_development_dependency 'mysql2'
27
28
  gem.add_development_dependency 'pg'
28
29
  gem.add_development_dependency 'sqlite3'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: with_advisory_lock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-20 00:00:00.000000000 Z
12
+ date: 2013-01-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -91,6 +91,22 @@ dependencies:
91
91
  - - ! '>='
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: mocha
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
94
110
  - !ruby/object:Gem::Dependency
95
111
  name: mysql2
96
112
  requirement: !ruby/object:Gem::Requirement
@@ -175,10 +191,12 @@ files:
175
191
  - lib/with_advisory_lock/mysql.rb
176
192
  - lib/with_advisory_lock/postgresql.rb
177
193
  - lib/with_advisory_lock/version.rb
194
+ - test/concern_test.rb
178
195
  - test/database.yml
179
196
  - test/minitest_helper.rb
197
+ - test/mysql_nesting_test.rb
198
+ - test/parallelism_test.rb
180
199
  - test/test_models.rb
181
- - test/with_advisory_lock_test.rb
182
200
  - with_advisory_lock.gemspec
183
201
  homepage: ''
184
202
  licenses: []
@@ -194,7 +212,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
194
212
  version: '0'
195
213
  segments:
196
214
  - 0
197
- hash: 2709177541654258140
215
+ hash: 1854060024724922654
198
216
  required_rubygems_version: !ruby/object:Gem::Requirement
199
217
  none: false
200
218
  requirements:
@@ -203,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
203
221
  version: '0'
204
222
  segments:
205
223
  - 0
206
- hash: 2709177541654258140
224
+ hash: 1854060024724922654
207
225
  requirements: []
208
226
  rubyforge_project:
209
227
  rubygems_version: 1.8.23
@@ -211,8 +229,10 @@ signing_key:
211
229
  specification_version: 3
212
230
  summary: Advisory locking for ActiveRecord
213
231
  test_files:
232
+ - test/concern_test.rb
214
233
  - test/database.yml
215
234
  - test/minitest_helper.rb
235
+ - test/mysql_nesting_test.rb
236
+ - test/parallelism_test.rb
216
237
  - test/test_models.rb
217
- - test/with_advisory_lock_test.rb
218
238
  has_rdoc: