diff --git a/lib/sqlite3/database.rb b/lib/sqlite3/database.rb index 40acf0d6..0f0b268a 100644 --- a/lib/sqlite3/database.rb +++ b/lib/sqlite3/database.rb @@ -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 @@ -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 diff --git a/test/test_integration_pending.rb b/test/test_integration_pending.rb index 102cb21e..56b6281e 100644 --- a/test/test_integration_pending.rb +++ b/test/test_integration_pending.rb @@ -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