tursodatabase/turso · error · ProgrammingError
query timeout must be non-negative
Error message
query timeout must be non-negative
What it means
Connection.set_query_timeout validates that milliseconds is >= 0 before calling into the engine; 0 disables the timeout. A negative value raises ProgrammingError immediately, since a deadline in the past is meaningless.
Source
Thrown at bindings/python/turso/lib.py:454
``OperationalError`` ("interrupted"). The underlying execution methods
release the GIL, so a watchdog thread can actually run while a query is
busy. If no statement is running the call is a no-op.
"""
try:
self._conn.interrupt()
except Exception as exc: # noqa: BLE001
raise _map_turso_exception(exc)
def set_query_timeout(self, milliseconds: int) -> None:
"""
Set the maximum time (in milliseconds) a single statement may run before
it is interrupted (raising ``OperationalError``). ``0`` disables the
timeout. Unlike ``interrupt()`` this needs no watchdog thread: the
deadline is enforced inside the engine. Turso extension (not in stdlib
``sqlite3``).
"""
if milliseconds < 0:
raise ProgrammingError("query timeout must be non-negative")
try:
self._conn.set_query_timeout(milliseconds)
except Exception as exc: # noqa: BLE001
raise _map_turso_exception(exc)
def get_query_timeout(self) -> int:
"""Return the current per-statement query timeout in milliseconds (``0`` = disabled)."""
try:
return self._conn.get_query_timeout()
except Exception as exc: # noqa: BLE001
raise _map_turso_exception(exc)
def _maybe_implicit_begin(self, sql: str) -> None:
"""
Implement sqlite3 legacy implicit transaction behavior:
If autocommit is LEGACY_TRANSACTION_CONTROL, isolation_level is not None, sql is a DML
(INSERT/UPDATE/DELETE/REPLACE), and there is no open transaction, issue:View on GitHub (pinned to bad083fafb)
Solutions
- Clamp computed values: conn.set_query_timeout(max(0, ms))
- Use 0 to disable the timeout, not a negative number
- If a computed timeout is already <= 0, skip the statement entirely — it is past budget
Example fix
# before
remaining_ms = int((deadline - time.time()) * 1000) # can be negative
conn.set_query_timeout(remaining_ms)
# after
remaining_ms = int((deadline - time.time()) * 1000)
if remaining_ms <= 0:
raise TimeoutError("query budget already exhausted")
conn.set_query_timeout(remaining_ms) Defensive patterns
Strategy: validation
Validate before calling
def safe_query_timeout(conn, ms: int) -> None:
"""0 disables the timeout; negative values are invalid."""
conn.set_query_timeout(max(0, int(ms))) Prevention
- Remember 0 means disabled — never -1
- With deadline arithmetic, check remaining budget before issuing the statement
- Pass get_query_timeout() values through unchanged only if you validated their origin
When it happens
Trigger: `conn.set_query_timeout(-1)` intending "no timeout" (the correct value is 0), or a computed timeout like `int((deadline - time.time()) * 1000)` that goes negative once the deadline passes.
Common situations: Deadline arithmetic racing the clock; request-scoped timeouts derived from already-expired budgets; copying semantics from APIs where -1 means infinite.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- no SQL statements to execute
- autocommit must be True, False, or 'LEGACY'
- size must be non-negative
- autocommit must be True, False, or 'LEGACY'
- retryFetch: attempts must be a finite integer >= 1, got ${at
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/29822dbf469dbaaf.
Report an issue: GitHub.