Skip to content

Commit 91c90af

Browse files
address comments
1 parent 547b422 commit 91c90af

3 files changed

Lines changed: 51 additions & 6 deletions

File tree

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). If the option is not specified, the lock is waited for indefinitely until it is acquired. See [Lock wait timeouts](https://github.com/ClosureTree/with_advisory_lock?tab=readme-ov-file#lock-wait-timeouts) in the with_advisory_lock gem for details.
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
@@ -25,23 +25,43 @@
2525
assert ClosureTree::Support.new(model, { with_advisory_lock: true, advisory_lock_timeout_seconds: 10 })
2626
end
2727

28+
it 'raises ArgumentError when advisory_lock_timeout_seconds is set but with_advisory_lock is false' do
29+
error = assert_raises(ArgumentError) do
30+
ClosureTree::Support.new(model, with_advisory_lock: false, advisory_lock_timeout_seconds: 10)
31+
end
32+
assert_match(/advisory_lock_timeout_seconds can't be specified when advisory_lock is disabled/, error.message)
33+
end
34+
2835
it 'calls :with_advisory_lock! when with_advisory_lock is true and timeout is 10' do
2936
options = sut.options.merge(with_advisory_lock: true, advisory_lock_timeout_seconds: 10)
37+
received_lock_name = nil
38+
received_options = nil
3039
called = false
3140
sut.stub(:options, options) do
32-
model.stub(:with_advisory_lock!, ->(_lock_name, _options, &block) { block.call }) do
41+
model.stub(:with_advisory_lock!, ->(lock_name, opts, &block) {
42+
received_lock_name = lock_name
43+
received_options = opts
44+
block.call
45+
}) do
3346
sut.with_advisory_lock { called = true }
3447
end
3548
end
3649
assert called, 'block should have been called'
50+
assert_equal sut.advisory_lock_name, received_lock_name, 'lock name should be passed to with_advisory_lock!'
51+
assert_equal({ timeout_seconds: 10 }, received_options, 'options should include timeout_seconds when timeout is configured')
3752
end
3853

3954
it 'calls :with_advisory_lock when with_advisory_lock is true and timeout is nil' do
55+
received_options = nil
4056
called = false
41-
model.stub(:with_advisory_lock, ->(_lock_name, _options, &block) { block.call }) do
57+
model.stub(:with_advisory_lock, ->(_lock_name, opts, &block) {
58+
received_options = opts
59+
block.call
60+
}) do
4261
sut.with_advisory_lock { called = true }
4362
end
4463
assert called, 'block should have been called'
64+
assert_equal({}, received_options, 'options should be empty when timeout is not configured')
4565
end
4666

4767
it 'does not call advisory lock methods when with_advisory_lock is false' do
@@ -52,4 +72,30 @@
5272
end
5373
assert called, 'block should have been called'
5474
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)