Skip to content

Commit b05600e

Browse files
committed
Add a lock_wait_timeout setter to set a busy_handler that releases the GVL between connection retries, but also errors after the timeout passes
1 parent 4f8ff6f commit b05600e

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

lib/sqlite3/database.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,19 @@ def readonly?
691691
@readonly
692692
end
693693

694+
def lock_wait_timeout=( milliseconds )
695+
timeout_seconds = milliseconds.fdiv(1000)
696+
retry_interval = 0.001 # 1 millisecond
697+
698+
busy_handler do |count|
699+
@start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) if count == 0
700+
701+
next sleep(retry_interval) unless (count % 100) == 0
702+
703+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_time) <= timeout_seconds
704+
end
705+
end
706+
694707
# A helper class for dealing with custom functions (see #create_function,
695708
# #create_aggregate, and #create_aggregate_handler). It encapsulates the
696709
# opaque function object that represents the current invocation. It also

test/test_integration_pending.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,33 @@ def test_busy_timeout
112112

113113
assert time.real*1000 >= 1000
114114
end
115+
116+
def test_lock_wait_timeout
117+
@db.lock_wait_timeout = 1000
118+
busy = Mutex.new
119+
busy.lock
120+
121+
t = Thread.new do
122+
begin
123+
db2 = SQLite3::Database.open( "test.db" )
124+
db2.transaction( :exclusive ) do
125+
busy.lock
126+
end
127+
ensure
128+
db2.close if db2
129+
end
130+
end
131+
132+
sleep 1
133+
time = Benchmark.measure do
134+
assert_raise( SQLite3::BusyException ) do
135+
@db.execute "insert into foo (b) values ( 'from 2' )"
136+
end
137+
end
138+
139+
busy.unlock
140+
t.join
141+
142+
assert time.real*1000 >= 1000
143+
end
115144
end

0 commit comments

Comments
 (0)