Skip to content

Commit 9194820

Browse files
authored
Merge pull request #1 from OpsLevel/13122-raise-error-if-we-cant-get-lock
Raise error when advisory lock cannot be acquired
2 parents 4a81efa + 43f6834 commit 9194820

8 files changed

+42
-12
lines changed

lib/closure_tree/finders.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def find_or_create_by_path(path, attributes = {})
1717
return found if found
1818

1919
attrs = subpath.shift
20-
_ct.with_advisory_lock do
20+
_ct.with_advisory_lock! do
2121
# shenanigans because children.create is bound to the superclass
2222
# (in the case of polymorphism):
2323
child = self.children.where(attrs).first || begin
@@ -159,7 +159,7 @@ def find_or_create_by_path(path, attributes = {})
159159
attr_path = _ct.build_ancestry_attr_path(path, attributes)
160160
find_by_path(attr_path) || begin
161161
root_attrs = attr_path.shift
162-
_ct.with_advisory_lock do
162+
_ct.with_advisory_lock! do
163163
# shenanigans because find_or_create can't infer that we want the same class as this:
164164
# Note that roots will already be constrained to this subclass (in the case of polymorphism):
165165
root = roots.where(root_attrs).first || _ct.create!(self, root_attrs)

lib/closure_tree/has_closure_tree.rb

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def has_closure_tree(options = {})
1212
:numeric_order,
1313
:touch,
1414
:with_advisory_lock,
15+
:advisory_lock_timeout_seconds,
1516
:order_belong_to
1617
)
1718

lib/closure_tree/hierarchy_maintenance.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def _ct_after_save
5353
end
5454

5555
def _ct_before_destroy
56-
_ct.with_advisory_lock do
56+
_ct.with_advisory_lock! do
5757
delete_hierarchy_references
5858
if _ct.options[:dependent] == :nullify
5959
self.class.find(self.id).children.find_each { |c| c.rebuild! }
@@ -63,7 +63,7 @@ def _ct_before_destroy
6363
end
6464

6565
def rebuild!(called_by_rebuild = false)
66-
_ct.with_advisory_lock do
66+
_ct.with_advisory_lock! do
6767
delete_hierarchy_references unless (defined? @was_new_record) && @was_new_record
6868
hierarchy_class.create!(:ancestor => self, :descendant => self, :generations => 0)
6969
unless root?
@@ -89,7 +89,7 @@ def rebuild!(called_by_rebuild = false)
8989
end
9090

9191
def delete_hierarchy_references
92-
_ct.with_advisory_lock do
92+
_ct.with_advisory_lock! do
9393
# The crazy double-wrapped sub-subselect works around MySQL's limitation of subselects on the same table that is being mutated.
9494
# It shouldn't affect performance of postgresql.
9595
# See http://dev.mysql.com/doc/refman/5.0/en/subquery-errors.html
@@ -111,7 +111,7 @@ module ClassMethods
111111
# Rebuilds the hierarchy table based on the parent_id column in the database.
112112
# Note that the hierarchy table will be truncated.
113113
def rebuild!
114-
_ct.with_advisory_lock do
114+
_ct.with_advisory_lock! do
115115
cleanup!
116116
roots.find_each { |n| n.send(:rebuild!) } # roots just uses the parent_id column, so this is safe.
117117
end

lib/closure_tree/numeric_deterministic_ordering.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def add_sibling(sibling, add_after = true)
125125
# Make sure self isn't dirty, because we're going to call reload:
126126
save
127127

128-
_ct.with_advisory_lock do
128+
_ct.with_advisory_lock! do
129129
prior_sibling_parent = sibling.parent
130130
reorder_from_value = if prior_sibling_parent == self.parent
131131
[self.order_value, sibling.order_value].compact.min

lib/closure_tree/support.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def initialize(model_class, options)
2323
:dependent => :nullify, # or :destroy or :delete_all -- see the README
2424
:name_column => 'name',
2525
:with_advisory_lock => true,
26+
:advisory_lock_timeout_seconds => 15,
2627
:numeric_order => false
2728
}.merge(options)
2829
raise ArgumentError, "name_column can't be 'path'" if options[:name_column] == 'path'
@@ -108,9 +109,9 @@ def where_eq(column_name, value)
108109
end
109110
end
110111

111-
def with_advisory_lock(&block)
112+
def with_advisory_lock!(&block)
112113
if options[:with_advisory_lock]
113-
model_class.with_advisory_lock(advisory_lock_name) do
114+
model_class.with_advisory_lock!(advisory_lock_name, advisory_lock_options) do
114115
transaction { yield }
115116
end
116117
else

lib/closure_tree/support_attributes.rb

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ def advisory_lock_name
88
Digest::SHA1.hexdigest("ClosureTree::#{base_class.name}")[0..32]
99
end
1010

11+
def advisory_lock_options
12+
{ timeout_seconds: options[:advisory_lock_timeout_seconds] }.compact
13+
end
14+
1115
def quoted_table_name
1216
connection.quote_table_name(table_name)
1317
end

spec/closure_tree/parallel_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def run_workers(worker_class = FindOrCreateWorker)
103103

104104
it 'creates dupe roots without advisory locks' do
105105
# disable with_advisory_lock:
106-
allow(Tag).to receive(:with_advisory_lock) { |_lock_name, &block| block.call }
106+
allow(Tag).to receive(:with_advisory_lock!) { |_lock_name, &block| block.call }
107107
run_workers
108108
# duplication from at least one iteration:
109109
expect(Tag.where(name: @names).size).to be > @iterations

spec/closure_tree/support_spec.rb

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
require 'spec_helper'
22

33
RSpec.describe ClosureTree::Support do
4-
let(:sut) { Tag._ct }
4+
let(:model) { Tag }
5+
let(:options) { {} }
6+
let(:sut) { described_class.new(model, options) }
7+
58
it 'passes through table names without prefix and suffix' do
69
expected = 'some_random_table_name'
710
expect(sut.remove_prefix_and_suffix(expected)).to eq(expected)
811
end
12+
913
it 'extracts through table name with prefix and suffix' do
1014
expected = 'some_random_table_name'
1115
tn = ActiveRecord::Base.table_name_prefix + expected + ActiveRecord::Base.table_name_suffix
1216
expect(sut.remove_prefix_and_suffix(tn)).to eq(expected)
1317
end
14-
end
18+
19+
[
20+
[true, 10, { timeout_seconds: 10 }],
21+
[true, nil, {}],
22+
[false, nil, {}]
23+
].each do |with_lock, timeout, expected_options|
24+
context "when with_advisory_lock is #{with_lock} and timeout is #{timeout}" do
25+
let(:options) { { with_advisory_lock: with_lock, advisory_lock_timeout_seconds: timeout } }
26+
27+
it "uses advisory lock with timeout options: #{expected_options}" do
28+
if with_lock
29+
expect(model).to receive(:with_advisory_lock!).with(anything, expected_options).and_yield
30+
else
31+
expect(model).not_to receive(:with_advisory_lock!)
32+
end
33+
34+
expect { |b| sut.with_advisory_lock!(&b) }.to yield_control
35+
end
36+
end
37+
end
38+
end

0 commit comments

Comments
 (0)