Unclean connection close on context cancellation during row scan
When a context is cancelled while scanning query results with rows.Scan, lib/pq closes the connection abruptly instead of gracefully. This surfaces as Connection reset by peer errors on the server side.
Behaviour
- Query executes successfully and begins returning rows
- Context is cancelled mid-scan (e.g. HTTP client disconnect)
- Server side observes:
Connection reset by peer (os error 104)
- Client side observes:
scan error: context canceled
Expected
Connection closed gracefully after context cancellation during rows.Scan, regardless of whether all rows have been consumed.
To Reproduce
docker-compose.yml:
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
pgdog:
image: ghcr.io/pgdogdev/pgdog:v0.1.38
volumes:
- ./pgdog.toml:/pgdog/pgdog.toml
- ./users.toml:/pgdog/users.toml
depends_on:
postgres:
condition: service_healthy
environment:
RUST_LOG: info
client:
image: golang:1.26-alpine
working_dir: /app
volumes:
- .:/app
environment:
- DSN=postgres://postgres:postgres@pgdog:6432/postgres?sslmode=disable
command: go run main.go
depends_on:
pgdog:
condition: service_started
pgdog.toml:
[general]
port = 6432
default_pool_size = 10
[[databases]]
name = "postgres"
host = "postgres"
port = 5432
users.toml:
[[users]]
name = "postgres"
database = "postgres"
password = "postgres"
go.mod:
module reproduce
go 1.26
require github.com/lib/pq v1.12.3
main.go:
package main
import (
"context"
"database/sql"
"log"
"os"
_ "github.com/lib/pq"
)
func main() {
dsn := os.Getenv("DSN")
if dsn == "" {
dsn = "postgres://postgres:postgres@127.0.0.1:6432/postgres?sslmode=disable"
}
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close()
if err := db.PingContext(context.Background()); err != nil {
log.Fatal("ping error:", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM generate_series(1, 1000000)")
if err != nil {
log.Println("query error:", err)
return
}
defer rows.Close()
count := 0
for rows.Next() {
var n int
if err := rows.Scan(&n); err != nil {
log.Println("scan error:", err)
return
}
count++
if count == 1000 {
cancel()
}
}
if err := rows.Err(); err != nil {
log.Println("rows error:", err)
}
}
Run go mod tidy to generate go.sum, then:
docker compose up --abort-on-container-exit
Observed output
Client:
scan error: context canceled
Server (pgdog proxy, making the TCP-level error observable):
ERROR client disconnected with error: net: io: Connection reset by peer (os error 104)
Environment
github.com/lib/pq v1.12.3
- Go
database/sql with QueryContext + rows.Scan
Unclean connection close on context cancellation during row scan
When a context is cancelled while scanning query results with
rows.Scan,lib/pqcloses the connection abruptly instead of gracefully. This surfaces asConnection reset by peererrors on the server side.Behaviour
Connection reset by peer (os error 104)scan error: context canceledExpected
Connection closed gracefully after context cancellation during
rows.Scan, regardless of whether all rows have been consumed.To Reproduce
docker-compose.yml:pgdog.toml:users.toml:go.mod:main.go:Run
go mod tidyto generatego.sum, then:Observed output
Client:
Server (pgdog proxy, making the TCP-level error observable):
Environment
github.com/lib/pq v1.12.3database/sqlwithQueryContext+rows.Scan