From 39d5e523a2779d76dad3c6fa246b7760b245b10c Mon Sep 17 00:00:00 2001 From: Vladimir Dementyev Date: Tue, 25 Aug 2026 14:28:37 +0300 Subject: [PATCH] Fix statement_timeout --- ext/sqlite3/database.c | 6 ++++-- ext/sqlite3/timespec.h | 9 +++++++++ test/test_integration_statement.rb | 10 ++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/ext/sqlite3/database.c b/ext/sqlite3/database.c index daadd74f..c096b4a7 100644 --- a/ext/sqlite3/database.c +++ b/ext/sqlite3/database.c @@ -417,8 +417,10 @@ rb_sqlite3_statement_timeout(void *context) clock_gettime(CLOCK_MONOTONIC, ¤tTime); if (!timespecisset(&ctx->stmt_deadline)) { - // Set stmt_deadline if not already set - ctx->stmt_deadline = currentTime; + struct timespec timeout; + timeout.tv_sec = ctx->stmt_timeout / 1000; + timeout.tv_nsec = (ctx->stmt_timeout % 1000) * 1000000L; + timespecadd(¤tTime, &timeout, &ctx->stmt_deadline); } else if (timespecafter(¤tTime, &ctx->stmt_deadline)) { return 1; } diff --git a/ext/sqlite3/timespec.h b/ext/sqlite3/timespec.h index 322fe758..66ee5c53 100644 --- a/ext/sqlite3/timespec.h +++ b/ext/sqlite3/timespec.h @@ -15,6 +15,15 @@ (vsp)->tv_nsec += 1000000000L; \ } \ } while (0) +#define timespecadd(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ + if ((vsp)->tv_nsec >= 1000000000L) { \ + (vsp)->tv_sec++; \ + (vsp)->tv_nsec -= 1000000000L; \ + } \ + } while (0) #define timespecafter(tsp, usp) \ (((tsp)->tv_sec > (usp)->tv_sec) || \ ((tsp)->tv_sec == (usp)->tv_sec && (tsp)->tv_nsec > (usp)->tv_nsec)) diff --git a/test/test_integration_statement.rb b/test/test_integration_statement.rb index 2e180acd..2f4bfcd8 100644 --- a/test/test_integration_statement.rb +++ b/test/test_integration_statement.rb @@ -205,6 +205,16 @@ def test_long_running_statements_get_interrupted_when_statement_timeout_set SELECT i FROM r ORDER BY i LIMIT 1; SQL end + + # (regression) Ensure queries that finish within the timeout complete, + # even ones running many instructions (time is instructions-based, not wall call) + @db.statement_timeout = 1_000 + result = @db.execute <<~SQL + WITH RECURSIVE r(i) AS (VALUES(0) UNION ALL SELECT i+1 FROM r LIMIT 100000) + SELECT count(i) FROM r; + SQL + assert_equal 100_000, result.first.first + ensure @db.statement_timeout = 0 end end