Skip to content

Unclean connection close on context cancellation during row scan #1325

Description

@matteosilv

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions