lib / pq

Pure Go Postgres driver for database/sql
https://pkg.go.dev/github.com/lib/pq
MIT License
9.04k stars 909 forks source link

PingContext doesn't respect the Context #1020

Open DeadlySurgeon opened 3 years ago

DeadlySurgeon commented 3 years ago

This just builds upon https://github.com/lib/pq/issues/786 to make sure it's not lost.

In our applications, we need to ping the database with what we determine is a sane timeout, to ensure that our execution isn't halted for too long, but since PingContext doesn't respect the context we are having issues when our database falls over in testing.

ealfatt commented 3 years ago

HI the problem is not solved, we are facing the same problem using latest version (ver. 1.10.3) Do you have in plan a new release in order to fix the issue? In the mean time, can you provide us a workaround to overcame the problem?

Thank You

xiaoxiaoHe-E commented 2 years ago

Hi, I also met this problem in version v1.3.5. The timeout in context can work when debugging in dlv and return an error context deadline exceeded. But cannot work in real runtime. Is there any update or workaround? Thank you.

0x-2a commented 2 years ago

It looks like this isn't the fault of PingContext anymore.

PingContext supports contexts now, but in debugging this, it seems the root of the cause is calling sql.Open(), or some other call to sql.Conn() followed by a call to PingContext after. The first two acquire the db.mu.lock, which PingContext ends up waiting on before it uses any timeout context: https://github.com/golang/go/blob/master/src/database/sql/sql.go#L1394

It becomes more clear when using conn.PingContext instead of db.PingContext, e.g. for a blocked port, this is how timeouts play out:

db, err := sql.Open(postgresDriver, connStr)
if err != nil {
    // This error will not happen
    log.Fatalln(err)
}

// Create a 1 second timeout
ctxBG := context.Background()
ctxConnTimeout, cancel := context.WithTimeout(ctxBG, 1*time.Second)
defer cancel()

conn, err := db.Conn(ctxConnTimeout) // Hangs here for 60 seconds, never uses my timeout
if err != nil {
        // This error fires
    log.Fatalln(err)
}

// Never got here
ctxPingTimeout, cancel := context.WithTimeout(ctxBG, 1*time.Second)
defer cancel()

err = conn.PingContext(ctxPingTimeout)
if err != nil {
    log.Fatalln(err)
}

So how to pass a timeout to sql.Open or sql.Conn? It seems this is the only way: sql.Open("postgres", "user=user dbname=dbname connect_timeout=5")