Skip to content

Commit cfe33f5

Browse files
address comments
1 parent 547b422 commit cfe33f5

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ When you include ```has_closure_tree``` in your model, you can provide a hash to
322322
* ```:order``` used to set up [deterministic ordering](#deterministic-ordering)
323323
* ```:scope``` restricts root nodes and sibling ordering to specific columns. Can be a single symbol or an array of symbols. Example: ```scope: :user_id``` or ```scope: [:user_id, :group_id]```. This ensures that root nodes and siblings are scoped correctly when reordering. See [Ordering Roots](#ordering-roots) for more details.
324324
* ```:touch``` delegates to the `belongs_to` annotation for the parent, so `touch`ing cascades to all children (the performance of this for deep trees isn't currently optimal).
325-
* ```:advisory_lock_timeout_seconds``` Raises an error when the advisory lock cannot be acquired within the timeout period
325+
* ```:advisory_lock_timeout_seconds``` When set, the advisory lock will raise `WithAdvisoryLock::FailedToAcquireLock` if the lock cannot be acquired within the timeout period. This helps callers handle timeout scenarios (e.g. retry or fail fast).
326326
327327
## Accessing Data
328328

lib/closure_tree/support.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,8 @@ def build_scope_where_clause(scope_conditions)
158158
end
159159

160160
def with_advisory_lock(&block)
161-
if options[:with_advisory_lock] && connection.supports_advisory_locks? && model_class.respond_to?(:with_advisory_lock!)
162-
lock_method = options[:advisory_lock_timeout_seconds].present? ? :with_advisory_lock! : :with_advisory_lock
163-
161+
lock_method = options[:advisory_lock_timeout_seconds].present? ? :with_advisory_lock! : :with_advisory_lock
162+
if options[:with_advisory_lock] && connection.supports_advisory_locks? && model_class.respond_to?(lock_method)
164163
model_class.public_send(lock_method, advisory_lock_name, advisory_lock_options) do
165164
transaction(&block)
166165
end

test/closure_tree/support_test.rb

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,34 @@
2727

2828
it 'calls :with_advisory_lock! when with_advisory_lock is true and timeout is 10' do
2929
options = sut.options.merge(with_advisory_lock: true, advisory_lock_timeout_seconds: 10)
30+
received_lock_name = nil
31+
received_options = nil
3032
called = false
3133
sut.stub(:options, options) do
32-
model.stub(:with_advisory_lock!, ->(_lock_name, _options, &block) { block.call }) do
34+
model.stub(:with_advisory_lock!, ->(lock_name, opts, &block) {
35+
received_lock_name = lock_name
36+
received_options = opts
37+
block.call
38+
}) do
3339
sut.with_advisory_lock { called = true }
3440
end
3541
end
3642
assert called, 'block should have been called'
43+
assert_equal sut.advisory_lock_name, received_lock_name, 'lock name should be passed to with_advisory_lock!'
44+
assert_equal({ timeout_seconds: 10 }, received_options, 'options should include timeout_seconds when timeout is configured')
3745
end
3846

3947
it 'calls :with_advisory_lock when with_advisory_lock is true and timeout is nil' do
48+
received_options = nil
4049
called = false
41-
model.stub(:with_advisory_lock, ->(_lock_name, _options, &block) { block.call }) do
50+
model.stub(:with_advisory_lock, ->(_lock_name, opts, &block) {
51+
received_options = opts
52+
block.call
53+
}) do
4254
sut.with_advisory_lock { called = true }
4355
end
4456
assert called, 'block should have been called'
57+
assert_equal({}, received_options, 'options should be empty when timeout is not configured')
4558
end
4659

4760
it 'does not call advisory lock methods when with_advisory_lock is false' do
@@ -52,4 +65,37 @@
5265
end
5366
assert called, 'block should have been called'
5467
end
68+
69+
it 'raises ArgumentError when advisory_lock_timeout_seconds is set but with_advisory_lock is false' do
70+
error = assert_raises(ArgumentError) do
71+
ClosureTree::Support.new(model, with_advisory_lock: false, advisory_lock_timeout_seconds: 10)
72+
end
73+
assert_match(/advisory_lock_timeout_seconds can't be specified when advisory_lock is disabled/, error.message)
74+
end
75+
76+
it 'raises WithAdvisoryLock::FailedToAcquireLock when lock cannot be acquired within timeout' do
77+
lock_held = false
78+
holder_thread = Thread.new do
79+
model.connection_pool.with_connection do
80+
model.with_advisory_lock(sut.advisory_lock_name) do
81+
lock_held = true
82+
sleep 2
83+
end
84+
end
85+
end
86+
87+
# Wait for holder to acquire the lock
88+
sleep 0.2 until lock_held
89+
90+
support_with_timeout = ClosureTree::Support.new(
91+
model,
92+
sut.options.merge(with_advisory_lock: true, advisory_lock_timeout_seconds: 1)
93+
)
94+
95+
assert_raises(WithAdvisoryLock::FailedToAcquireLock) do
96+
support_with_timeout.with_advisory_lock { nil }
97+
end
98+
99+
holder_thread.join
100+
end
55101
end

0 commit comments

Comments
 (0)