diff --git a/Gemfile.lock b/Gemfile.lock index de57a16..61e4574 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - redis-read-write-locks (0.5.0) + redis-read-write-locks (0.5.1) GEM remote: https://rubygems.org/ diff --git a/lib/redis_read_write_locks/base_lock.rb b/lib/redis_read_write_locks/base_lock.rb index e76c5d1..48d6d53 100644 --- a/lib/redis_read_write_locks/base_lock.rb +++ b/lib/redis_read_write_locks/base_lock.rb @@ -47,12 +47,12 @@ def acquire(retry_count: nil, retry_delay: DEFAULT_RETRY_DELAY) def synchronize(retry_count: nil, retry_delay: DEFAULT_RETRY_DELAY, &block) acquire_or_raise(retry_count: retry_count, retry_delay: retry_delay) - stopped = false - watchdog = start_watchdog(Thread.current, -> { stopped }) + stop = Thread::Queue.new + watchdog = start_watchdog(Thread.current, stop) begin block.call ensure - stopped = true + stop.close watchdog.join release end @@ -66,13 +66,18 @@ def acquire_or_raise(retry_count:, retry_delay:) acquire || raise(LockNotAcquiredError, "Could not acquire #{lock_type} lock '#{@name}'") end - def start_watchdog(main_thread, stopped) + # Waits on the queue rather than sleeping, so closing it on release wakes the + # watchdog at once. While it slept, every synchronize paid up to a full + # WATCHDOG_SLEEP_INTERVAL on the way out - far more than a short critical + # section takes, and callers that take many brief locks paid it every time. + def start_watchdog(main_thread, stop) Thread.new do elapsed = 0.0 - until stopped.call - sleep WATCHDOG_SLEEP_INTERVAL + loop do + stop.pop(timeout: WATCHDOG_SLEEP_INTERVAL) + break if stop.closed? + elapsed += WATCHDOG_SLEEP_INTERVAL - next if stopped.call next unless elapsed >= WATCHDOG_REFRESH_INTERVAL elapsed = 0.0 diff --git a/lib/redis_read_write_locks/version.rb b/lib/redis_read_write_locks/version.rb index 3d1f8af..0bd31a4 100644 --- a/lib/redis_read_write_locks/version.rb +++ b/lib/redis_read_write_locks/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module RedisReadWriteLocks - VERSION = "0.5.0" + VERSION = "0.5.1" end diff --git a/spec/redis_read_write_locks/write_lock_spec.rb b/spec/redis_read_write_locks/write_lock_spec.rb index 6fbc600..66c6785 100644 --- a/spec/redis_read_write_locks/write_lock_spec.rb +++ b/spec/redis_read_write_locks/write_lock_spec.rb @@ -126,6 +126,16 @@ expect { lock.synchronize {} }.to raise_error(RedisReadWriteLocks::LockNotAcquiredError) end + it "releases without waiting out the watchdog's sleep interval" do + stub_const("RedisReadWriteLocks::BaseLock::WATCHDOG_SLEEP_INTERVAL", 5) + + started = Process.clock_gettime(Process::CLOCK_MONOTONIC) + lock.synchronize { sleep 0.05 } + elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started + + expect(elapsed).to be < 1 + end + it "watchdog keeps lock alive beyond TTL" do stub_const("RedisReadWriteLocks::BaseLock::WATCHDOG_REFRESH_INTERVAL", 0.1) stub_const("RedisReadWriteLocks::BaseLock::WATCHDOG_SLEEP_INTERVAL", 0.05)