Skip to content

libsql-sqlite3: Make libsql_stmt_interrupt() abort an in-flight step#2248

Merged
penberg merged 3 commits into
mainfrom
stmt-interrupt
Jun 2, 2026
Merged

libsql-sqlite3: Make libsql_stmt_interrupt() abort an in-flight step#2248
penberg merged 3 commits into
mainfrom
stmt-interrupt

Conversation

@penberg
Copy link
Copy Markdown
Collaborator

@penberg penberg commented Jun 2, 2026

libsql_stmt_interrupt() set a per-statement isInterrupted flag, but the
VDBE execution loop only ever checked the connection-wide
db->u1.isInterrupted. A statement already executing inside sqlite3_step()
therefore ran to completion regardless of the request; the flag was only
observed at the next step() entry.

Check p->isInterrupted alongside db->u1.isInterrupted at both VDBE
interrupt-check sites so an interrupt requested mid-execution aborts the
running statement promptly with SQLITE_INTERRUPT, without touching the
connection-wide interrupt state (other statements keep running). Set and
clear the flag atomically, mirroring sqlite3_interrupt(), since the
request may come from another thread.

Tests:

  • test/interruptstmt.test: deterministic regression via a new
    sqlite_stmt_interrupt_count test hook, covering the in-loop check and
    the connection-flag-stays-clear property.
  • test/interrupttest.c: standalone multi-threaded test of the real
    cross-thread case (interrupt a step() in flight) and statement-level
    granularity. Build and run with make interrupttest.

penberg added 3 commits June 2, 2026 08:38
libsql_stmt_interrupt() set a per-statement isInterrupted flag, but the
VDBE execution loop only ever checked the connection-wide
db->u1.isInterrupted. A statement already executing inside sqlite3_step()
therefore ran to completion regardless of the request; the flag was only
observed at the next step() entry.

Check p->isInterrupted alongside db->u1.isInterrupted at both VDBE
interrupt-check sites so an interrupt requested mid-execution aborts the
running statement promptly with SQLITE_INTERRUPT, without touching the
connection-wide interrupt state (other statements keep running). Set and
clear the flag atomically, mirroring sqlite3_interrupt(), since the
request may come from another thread.

Tests:
- test/interruptstmt.test: deterministic regression via a new
  sqlite_stmt_interrupt_count test hook, covering the in-loop check and
  the connection-flag-stays-clear property.
- test/interrupttest.c: standalone multi-threaded test of the real
  cross-thread case (interrupt a step() in flight) and statement-level
  granularity. Build and run with `make interrupttest`.
The 0.10.0-pre.3 version bump (61d629a) updated the workspace
Cargo.toml versions but not Cargo.lock, which stayed at 0.10.0-pre.2.
Any cargo invocation (including the c-bundle-validate CI job's
'cargo xtask build-bundled') rewrites the lockfile to match, leaving an
uncommitted Cargo.lock change that fails the job's 'git diff --quiet'
check. Regenerate the lockfile so it matches the committed Cargo.toml.
@penberg penberg merged commit fe31b35 into main Jun 2, 2026
20 checks passed
@penberg penberg deleted the stmt-interrupt branch June 2, 2026 06:09
penberg added a commit to tursodatabase/libsql-js that referenced this pull request Jun 2, 2026
Query timeouts previously relied on a per-connection execution lock that
serialized every operation so the timer wheel's connection.interrupt() would
unambiguously hit the timed-out operation. This serialized all concurrent work
on a connection and added a mutex acquisition to every call.

Use libsql_stmt_interrupt() instead: the timer wheel now interrupts the
specific statement that timed out (via a new Interruptible trait, so the wheel
can target either a statement or a connection), leaving other concurrent
operations on the same connection untouched. This removes execution_lock
entirely, along with acquire_execution_lock and the deadline/remaining
plumbing it required.

Depends on: tursodatabase/libsql#2248
penberg added a commit to tursodatabase/libsql-js that referenced this pull request Jun 2, 2026
Query timeouts previously relied on a per-connection execution lock that
serialized every operation so the timer wheel's connection.interrupt() would
unambiguously hit the timed-out operation. This serialized all concurrent work
on a connection and added a mutex acquisition to every call.

Use libsql_stmt_interrupt() instead: the timer wheel now interrupts the
specific statement that timed out (via a new Interruptible trait, so the wheel
can target either a statement or a connection), leaving other concurrent
operations on the same connection untouched. This removes execution_lock
entirely, along with acquire_execution_lock and the deadline/remaining
plumbing it required.

Depends on: tursodatabase/libsql#2248
penberg added a commit to tursodatabase/libsql-js that referenced this pull request Jun 2, 2026
…#226)

Query timeouts previously relied on a per-connection execution lock that
serialized every operation so the timer wheel's connection.interrupt()
would
unambiguously hit the timed-out operation. This serialized all
concurrent work
on a connection and added a mutex acquisition to every call.

Use libsql_stmt_interrupt() instead: the timer wheel now interrupts the
specific statement that timed out (via a new Interruptible trait, so the
wheel
can target either a statement or a connection), leaving other concurrent
operations on the same connection untouched. This removes execution_lock
entirely, along with acquire_execution_lock and the deadline/remaining
plumbing it required.

Depends on: tursodatabase/libsql#2248
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant