This error occurs when a database operation is intentionally interrupted using the sqlite3_interrupt() function, typically in response to user cancellation. Handle it by properly finalizing interrupted statements and implementing timeout mechanisms.
SQLITE_INTERRUPT (error code 9) is returned when a running SQL operation is terminated by calling sqlite3_interrupt(). This is not a bugβit's an intentional signal that an in-progress query or transaction has been cancelled. The sqlite3_interrupt() function is designed to halt long-running operations at the earliest safe point, making it useful for implementing query timeouts or responding to user cancellation requests.
After receiving SQLITE_INTERRUPT, always call sqlite3_finalize() on the prepared statement to release resources. Failing to finalize causes memory leaks.
sqlite3_stmt *stmt;
int rc = sqlite3_step(stmt);
if (rc == SQLITE_INTERRUPT) {
// Operation was interrupted
sqlite3_finalize(stmt); // Always finalize to avoid memory leak
}Determine whether the interrupt was expected (user-initiated) or unexpected (timeout). If intentional, handle gracefully. If unexpected, log the error and investigate the cause.
if (rc == SQLITE_INTERRUPT) {
if (user_cancelled) {
// Expected cancellation - inform user operation was cancelled
log_user_message("Query cancelled by user");
} else {
// Unexpected interruption - log for debugging
log_error("Unexpected SQLITE_INTERRUPT on query");
}
}If using sqlite3_interrupt() for timeouts, spawn a timer in a separate thread that calls sqlite3_interrupt() if a query exceeds the time limit. Always pair this with proper statement finalization.
// Timer thread that interrupts after timeout
void* timeout_thread(void* arg) {
sqlite3 *db = (sqlite3*)arg;
sleep(QUERY_TIMEOUT_SECONDS);
sqlite3_interrupt(db);
return NULL;
}
// Main query execution
pthread_t timer;
pthread_create(&timer, NULL, timeout_thread, db);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_INTERRUPT) {
sqlite3_finalize(stmt);
}
pthread_join(timer, NULL);For more controlled interruption without threading concerns, use sqlite3_progress_handler(). It allows you to check operation progress and return non-zero to stop execution, without risking closed database connections.
int progress_callback(void *arg) {
time_t *start = (time_t*)arg;
time_t elapsed = time(NULL) - *start;
if (elapsed > QUERY_TIMEOUT_SECONDS) {
return 1; // Non-zero return stops execution
}
return 0; // Zero return continues execution
}
time_t query_start = time(NULL);
sqlite3_progress_handler(db, PROGRESS_OPCODES, progress_callback, &query_start);
int rc = sqlite3_step(stmt);If sqlite3_interrupt() is called during an INSERT, UPDATE, or DELETE within a transaction, the entire transaction is automatically rolled back. Verify transaction state after an interrupt and either retry or inform the user.
int rc = sqlite3_step(stmt);
if (rc == SQLITE_INTERRUPT) {
sqlite3_finalize(stmt);
// Transaction is automatically rolled back
// Check if we need to retry or log the failure
char *errmsg = sqlite3_errmsg(db);
log_error("Transaction interrupted: %s", errmsg);
// Optionally retry the operation
retry_operation();
}Thread Safety: sqlite3_interrupt() is thread-safe and can be called from a different thread than the one executing the query. However, do not call sqlite3_interrupt() on a database connection that might close before the call completes, as this is undefined behavior. Duration of Effect: Once sqlite3_interrupt() is called, the interrupt flag remains active until all currently running statements complete. Any new statements started before the running statement count reaches zero will also be interrupted. After all statements finish, new statements are not affected. Checking Interrupt Status: Use sqlite3_is_interrupted(db) to check if an interrupt is currently in effect for a connection. This is useful in long-running loops to determine if a user has requested cancellation. Performance: The interrupt check only occurs at VDBE loop points during sqlite3_step(), not during prepare or finalize, so very fast queries may not be interruptible.
SQLITE_BUSY: The database file is locked
How to fix 'SQLITE_BUSY: The database file is locked' in SQLite
better-sqlite3: This statement has already been finalized
How to fix "better-sqlite3: This statement has already been finalized" in SQLite
SQLITE_AUTH: Authorization denied
SQLITE_AUTH: Authorization denied
SQLITE_CONSTRAINT_CHECK: CHECK constraint failed
CHECK constraint failed in SQLite
SQLITE_READONLY_RECOVERY: WAL mode database cannot be modified
How to fix "SQLITE_READONLY_RECOVERY: WAL mode database cannot be modified" in SQLite