stray query cancellation
This has been tracked down from a production issue.
I'll lead with the reproducer. Run with -race.
require modernc.org/sqlite v1.42.1
package main
import (
"context"
"database/sql"
"fmt"
"strings"
_ "modernc.org/sqlite"
)
func main() {
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
panic(err)
}
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
conn, err := db.Conn(context.Background())
if err != nil {
panic(err)
}
for i := 0; ; i++ {
ctx, cancel := context.WithCancel(context.Background())
go cancel() // cancel "very soon" but not immediately
conn.ExecContext(ctx, "SELECT 1") // might be interrupted, don't care
// This query should never be interrupted, because it uses a non-cancellable context.
_, err := conn.ExecContext(context.Background(), "SELECT 1")
if err != nil && strings.Contains(err.Error(), "interrupted") {
panic(err)
}
if i%1_00_000 == 0 {
fmt.Printf("iteration %d\n", i)
}
}
}
It can take a very long time to fail on an idle machine, but running many instances concurrently triggers failures very quickly.
Sample local run:
$ go build -race .
$ ./sqlite-interrupt-bug # can run for a long time, but eventually fails
iteration 0
^C
$ stress ./sqlite-interrupt-bug # fails very quickly
/var/folders/vm/htvrhp4177v2dfhdjlvl0pqh0000gn/T/go-stress-20251227T162220-643681232
iteration 0
panic: interrupted (9)
^C
Again, this is happening to us semi-regularly in prod right now, so this is not just theoretical.
Edited by Josh Bleecher Snyder