Skip to content

Commit

Permalink
Fix bug with busy_handler_timeout and write better tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fractaledmind committed Jan 2, 2024
1 parent 9444b9b commit 48daae1
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 22 deletions.
13 changes: 9 additions & 4 deletions lib/sqlite3/database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def initialize file, options = {}, zvfs = nil
@type_translator = make_type_translator @type_translation
@readonly = mode & Constants::Open::READONLY != 0
@default_transaction_mode = options[:default_transaction_mode] || :deferred
@timeout_deadline = nil

if block_given?
begin
Expand Down Expand Up @@ -697,12 +698,16 @@ def readonly?
# while SQLite sleeps and retries.
def busy_handler_timeout=( milliseconds )
timeout_seconds = milliseconds.fdiv(1000)
timeout_deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout_seconds

busy_handler do |count|
next false if Process.clock_gettime(Process::CLOCK_MONOTONIC) > timeout_deadline

sleep(0.001)
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
if count.zero?
@timeout_deadline = now + timeout_seconds
elsif now > @timeout_deadline
next false
else
sleep(0.001)
end
end
end

Expand Down
45 changes: 27 additions & 18 deletions test/test_integration_pending.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,30 +113,39 @@ def test_busy_timeout
assert time.real*1000 >= 1000
end

def test_busy_timeout_blocks_gvl_while_busy_handler_timeout_releases_gvl
busy_timeout_db = SQLite3::Database.open( "test.db" )
busy_timeout_db.busy_timeout 1000
busy_timeout_time = Benchmark.measure do
@db.transaction( :exclusive ) do
assert_raises( SQLite3::BusyException ) do
busy_timeout_db.transaction( :exclusive ) {}
def test_busy_timeout_blocks_gvl
threads = [1, 2].map do
Thread.new do
db = SQLite3::Database.new("test.db")
db.busy_timeout = 3000
db.transaction(:immediate) do
db.execute "insert into foo ( b ) values ( ? )", rand(1000).to_s
sleep 1
db.execute "insert into foo ( b ) values ( ? )", rand(1000).to_s
end
end
end
busy_timeout_db.close

busy_handler_timeout_db = SQLite3::Database.open( "test.db" )
busy_handler_timeout_db.busy_handler_timeout = 1000
busy_handler_timeout_time = Benchmark.measure do
@db.transaction( :exclusive ) do
assert_raises( SQLite3::BusyException ) do
busy_handler_timeout_db.transaction( :exclusive ) {}

assert_raise( SQLite3::BusyException ) do
threads.each(&:join)
end
end

def test_busy_handler_timeout_releases_gvl
threads = [1, 2].map do
Thread.new do
db = SQLite3::Database.new("test.db")
db.busy_handler_timeout = 3000
db.transaction(:immediate) do
db.execute "insert into foo ( b ) values ( ? )", rand(1000).to_s
sleep 1
db.execute "insert into foo ( b ) values ( ? )", rand(1000).to_s
end
end
end
busy_handler_timeout_db.close

assert_operator busy_timeout_time.real, :>, busy_handler_timeout_time.real
assert_operator busy_timeout_time.real, :>, busy_handler_timeout_time.real+1
assert_nothing_raised do
threads.each(&:join)
end
end
end

0 comments on commit 48daae1

Please sign in to comment.