You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
dangling transactions after 'failed to deallocate cached statement' errors #2100
Hey there. First of all, thank you for all your work on this library 👍
Describe the bug
We are using pgx in a fairly typical backend application, that serves requests to clients via grpc. We use pgx as a driver via sqlx. Starting with version 5.5.4, we started to notice some problems:
Our production logs contained lines "failed to deallocate cached statement: conn closed". Roughly it was a couple hundred times a day, for a couple hundred thousand requests a day where each request is a transaction.
This happened predominantly (but not exclusively) in streaming requests. Those are ended when cancelled by the user, so we assume it's some race condition around cancelled context behavior.
The logged error happens from a call to tx.Rollback(), which is called from a defer that is set up with the transaction and run at the end of the cancelled rpc method.
We also started to see dangling transactions left in an idle in transaction state, indefinitely (hours), causing issues particularly when they held on to locks. While this is not definitely related to the log entries, it started happening at the same time and the log entries indicate an error during transaction cleanup.
It seems like something goes wrong around transaction cleanup, but we can't tell what's going on at all.
To Reproduce
Unfortunately, we were unable to reproduce this bug outside our production environment, which also isn't source available. :( Rolling back to 5.5.3 "fixed" the issue for us, so it might be caused by 046f497. We tried some things in our codebase around transaction behavior and made sure we begin/end transactions consistently throughout, to no avail. If you have any ideas, we'd be happy to try.
Version
Go: $ go version -> go version go1.21.5 linux/amd64
changed the title [-]dangling transactions after f'failed to deallocate cached statement' errors[/-][+]dangling transactions after 'failed to deallocate cached statement' errors[/+]on Aug 1, 2024
I do not think that failed to deallocate cached statement: conn closed is directly connected to dangling transactions -- after all the connection is closed -- it can't be holding open a connection. I suppose it could be worth checking that the connection actually is closed on the server though.
I suppose it's possible this has something to do with the context cancellation. But because of the difficulty in reliably interrupting a query and recovering the connection, pgx simply closes the connection when a query is interrupted. So it would seem there's not much that could go wrong there.
👋
I'm seeing some similar errors when using a "child" context to query some rows for locking.
So i'm doing basically this:
tctx, cancel := context.WithTimeout(ctx, 10 * time.Second)
defer cancel()
txx, err := s.sqlClient.BeginTxx(tctx, nil)
defer func() {
// handle recover
fn := txx.Commit
if err != nil {
fn = txx.Rollback
}
funcErr := fn()
if funErr != nil {
log.Error("error applying transaction %+v", funcErr)
// Here i see the error with `failed to deallocate cached statement: conn closed `
err = fmt.Errorf("failed to apply transaction %w", funErr)
}
return err
}
childCtx, cancelChildren := context.WithTimeout(tctx, 1 * time.Secoond)
defer cancelChildren()
var ids []string
err := s.sqlClient.ExecCtx(childCtx, &ids, "select id from users where age > 99 for update"); // this takes more than 1 second
if err != nil {
return err
}
// use the ids to run an update
// loop through users and run an update
// commit transaction
After the "lock" queries started hitting their context timeout, i started seeing this errors on the application logs. Is it the case that this "child" context being cancelled is closing down the original transaction connection ?
I'm trying to understand where in the code does this happen if there's something I can do to better handle this (I already want to refactor how the transaction is committed/rollbacked to avoid failing)
Is it the case that this "child" context being cancelled is closing down the original transaction connection ?
Yes. Context cancellation closes the connection in most cases because it is very difficult or impossible to reliably recover from an interrupted query.
But beyond that, the fundamental means of interrupting a query in PostgreSQL causes the interrupted query to return an error. If a query returns an error the wrapping transaction is places in an error state. So even at the PostgreSQL level the only way to handle this would be to use savepoints (pseudo nested transactions) around every query you might want to cancel and rollback.
I've encountered this dangling transactions issue with another project, but downgrading to v5.5.3 hasn't helped. Perhaps setting a low value for idle_in_transaction_session_timeout will "fix" it for me.
Drive-by comment as I've just been debugging a similar issue. For those experiencing this issue, is it possible that you're using the *pgx.Conn concurrently? Such concurrent use could potentially trigger this error message.
Run the provided Go code as a gRPC service. The SQL queries in the service take between 0.5s and 1.5s to execute.
Run an HTTP server that calls this gRPC service. The key point is that the gRPC client timeout is set to 1s.
Finally, perform a load test on the HTTP layer by sending a large volume of requests.
# go log
Commit: 140122_d8dee5c2-08d2-49f5-861e-008c1c81a23b failed to deallocate cached statement(s): conn closed
# sql info
INFO ThreadId(12) Client 192.168.xx.xx:58445 issued a cancel query request
INFO ThreadId(12) Client 192.168.xx.xx:58446 issued a cancel query request
WARN ThreadId(12) Server returned while still in transaction, rolling back transaction
WARN ThreadId(09) Server returned while still in transaction, rolling back transaction
@mingjunyang I don't have a Rust build environment handy, so I can't test your example. But I reviewed the code and I did spot some anomalies that may be factors.
The defers in SayHello are out of order. Below is that function with everything removed except the lines that need a defer to ensure cleanup and the actual cleanup calls.
pool, err:=s.pool.Acquire(ctx)
tx, err:=pool.Begin(ctx)
defertx.Rollback(ctx)
rows, err:=tx.Query(ctx, "SELECT 'data' ,pg_sleep(0.5 + RANDOM()) where $1 = $1", in_name)
deferrows.Close()
err=tx.Commit(ctx)
deferpool.Release()
Defers happen in reverse order so it would be possible to release the pool before the tx is committed or rolled back. Not sure if that is actually what's happening here, but it's suspicious.
Also, I would recommend renaming the pool variable to conn. Connections are checked out of the pool by Acquire. The pool itself isn't checked out.
@Valodim Hi,
look for potential deadlocks e.g. opening a transaction and inside the transaction you try to acquire another connection from the pool. Verify your connection pool has enough capacity to serve the requests, when you run into a timeout. When a timeout happens the transaction is dropped but it's not yet commited that's why the statement cache does not find anything.
I'll also suggest to use pgxs beginFunc as it handles most of the transaction error cases. We had the same problem. :)
Activity
[-]dangling transactions after f'failed to deallocate cached statement' errors[/-][+]dangling transactions after 'failed to deallocate cached statement' errors[/+]jackc commentedon Aug 3, 2024
I do not think that
failed to deallocate cached statement: conn closedis directly connected to dangling transactions -- after all the connection is closed -- it can't be holding open a connection. I suppose it could be worth checking that the connection actually is closed on the server though.I suppose it's possible this has something to do with the context cancellation. But because of the difficulty in reliably interrupting a query and recovering the connection, pgx simply closes the connection when a query is interrupted. So it would seem there's not much that could go wrong there.
xocasdashdash commentedon Oct 3, 2024
👋
I'm seeing some similar errors when using a "child" context to query some rows for locking.
So i'm doing basically this:
After the "lock" queries started hitting their context timeout, i started seeing this errors on the application logs. Is it the case that this "child" context being cancelled is closing down the original transaction connection ?
I'm trying to understand where in the code does this happen if there's something I can do to better handle this (I already want to refactor how the transaction is committed/rollbacked to avoid failing)
jackc commentedon Oct 5, 2024
@xocasdashdash
Yes. Context cancellation closes the connection in most cases because it is very difficult or impossible to reliably recover from an interrupted query.
But beyond that, the fundamental means of interrupting a query in PostgreSQL causes the interrupted query to return an error. If a query returns an error the wrapping transaction is places in an error state. So even at the PostgreSQL level the only way to handle this would be to use savepoints (pseudo nested transactions) around every query you might want to cancel and rollback.
Pin pgx at v5.5.3 due to jackc/pgx#2100
robstradling commentedon Mar 24, 2025
I've encountered this dangling transactions issue with another project, but downgrading to v5.5.3 hasn't helped. Perhaps setting a low value for idle_in_transaction_session_timeout will "fix" it for me.
twpayne commentedon May 3, 2025
Drive-by comment as I've just been debugging a similar issue. For those experiencing this issue, is it possible that you're using the
*pgx.Connconcurrently? Such concurrent use could potentially trigger this error message.mingjunyang commentedon Jan 7, 2026
I have a demo here to reproduce the scenario.
Run the provided Go code as a gRPC service. The SQL queries in the service take between 0.5s and 1.5s to execute.
Run an HTTP server that calls this gRPC service. The key point is that the gRPC client timeout is set to 1s.
Finally, perform a load test on the HTTP layer by sending a large volume of requests.
gRPC Server
HTTP Server
http benchmark
riking commentedon Jan 13, 2026
Adding to the above, this issue caused a partial outage of a CT Log server:
https://groups.google.com/a/chromium.org/d/msgid/ct-policy/5bd292eb-9485-4e85-9c3a-379f604f2a6fn%40chromium.org?utm_medium=email&utm_source=footer
jackc commentedon Jan 24, 2026
@mingjunyang I don't have a Rust build environment handy, so I can't test your example. But I reviewed the code and I did spot some anomalies that may be factors.
The defers in
SayHelloare out of order. Below is that function with everything removed except the lines that need a defer to ensure cleanup and the actual cleanup calls.Defers happen in reverse order so it would be possible to release the pool before the tx is committed or rolled back. Not sure if that is actually what's happening here, but it's suspicious.
Also, I would recommend renaming the
poolvariable toconn. Connections are checked out of the pool byAcquire. The pool itself isn't checked out.Mitigate PostgreSQL connection pool contamination
Mitigate PostgreSQL connection pool contamination
fix(postgres): handle conn closed err (#16066)
fix(postgres): handle conn closed err (kumahq#16066)
tilomangin commentedon May 26, 2026
@Valodim Hi,
look for potential deadlocks e.g. opening a transaction and inside the transaction you try to acquire another connection from the pool. Verify your connection pool has enough capacity to serve the requests, when you run into a timeout. When a timeout happens the transaction is dropped but it's not yet commited that's why the statement cache does not find anything.
I'll also suggest to use pgxs beginFunc as it handles most of the transaction error cases. We had the same problem. :)