Skip to content

Commit 0d487ae

Browse files
authored
Merge pull request #443 from fractaledmind/lock-wait-timeout
Add a busy_handler_timeout setter
2 parents f80c5ff + 0c93d30 commit 0d487ae

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

lib/sqlite3/database.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ def initialize file, options = {}, zvfs = nil
131131
@type_translator = make_type_translator @type_translation
132132
@readonly = mode & Constants::Open::READONLY != 0
133133
@default_transaction_mode = options[:default_transaction_mode] || :deferred
134+
@timeout_deadline = nil
134135

135136
if block_given?
136137
begin
@@ -698,6 +699,25 @@ def readonly?
698699
@readonly
699700
end
700701

702+
# Sets a #busy_handler that releases the GVL between retries,
703+
# but only retries up to the indicated number of +milliseconds+.
704+
# This is an alternative to #busy_timeout, which holds the GVL
705+
# while SQLite sleeps and retries.
706+
def busy_handler_timeout=( milliseconds )
707+
timeout_seconds = milliseconds.fdiv(1000)
708+
709+
busy_handler do |count|
710+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
711+
if count.zero?
712+
@timeout_deadline = now + timeout_seconds
713+
elsif now > @timeout_deadline
714+
next false
715+
else
716+
sleep(0.001)
717+
end
718+
end
719+
end
720+
701721
# A helper class for dealing with custom functions (see #create_function,
702722
# #create_aggregate, and #create_aggregate_handler). It encapsulates the
703723
# opaque function object that represents the current invocation. It also

test/test_integration_pending.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,48 @@ def test_busy_timeout
7979

8080
assert time.real*1000 >= 1000
8181
end
82+
83+
def test_busy_timeout_blocks_gvl
84+
threads = [1, 2].map do
85+
Thread.new do
86+
begin
87+
db = SQLite3::Database.new("test.db")
88+
db.busy_timeout = 3000
89+
db.transaction(:immediate) do
90+
db.execute "insert into foo ( b ) values ( ? )", rand(1000).to_s
91+
sleep 1
92+
db.execute "insert into foo ( b ) values ( ? )", rand(1000).to_s
93+
end
94+
ensure
95+
db.close if db
96+
end
97+
end
98+
end
99+
100+
assert_raise( SQLite3::BusyException ) do
101+
threads.each(&:join)
102+
end
103+
end
104+
105+
def test_busy_handler_timeout_releases_gvl
106+
threads = [1, 2].map do
107+
Thread.new do
108+
begin
109+
db = SQLite3::Database.new("test.db")
110+
db.busy_handler_timeout = 3000
111+
db.transaction(:immediate) do
112+
db.execute "insert into foo ( b ) values ( ? )", rand(1000).to_s
113+
sleep 1
114+
db.execute "insert into foo ( b ) values ( ? )", rand(1000).to_s
115+
end
116+
ensure
117+
db.close if db
118+
end
119+
end
120+
end
121+
122+
assert_nothing_raised do
123+
threads.each(&:join)
124+
end
125+
end
82126
end

0 commit comments

Comments
 (0)