feat: evict connections with open transactions on pool release#3597
feat: evict connections with open transactions on pool release#3597panga wants to merge 9 commits intobrianc:masterfrom
Conversation
|
@charmander thanks for the review. I added a log to align with the behavior in other cases. I couldn’t find a clean way to surface an error at that point since Do you think this behavior change should be behind a pool option and disabled by default? |
Though I think how the pool behaves now (doesn't have any concept of the client being in a transaction and will happily rent it out to another caller later) is almost always wrong, I think it risks weird behavioral breaking changes if we change the default behavior in the So for pg@8.x it would be To opt into the behavior. Then in 9.x we can make this behavior the default & actually remove the feature flag entirely since I don't see any reason to allow the old, invalid behavior long term.
I agree w/ @panga that |
My suggestion was to throw an error synchronously from Exposing the status as a public property is a good idea for other reasons, though. |
Yeah that makes sense too. I'll work on a PR soon for the |
|
I implemented the recommended changes for the new pool option I'm concerned that throwing errors from const client = await pool.connect();
try {
return await client.query(...);
} catch (err) {
client.release(err);
} finally {
client.release();
}Edit: The above example is not correct, it need to catch the err to avoid double release. try {
...
} catch (err) {
clientErr = err
} finally {
client.release(clientErr)
} |
Yes, that’s normal JavaScript behaviour for error conditions. Note that this would not be an uncatchable exception, and would typically not terminate the process in applications that care about surviving unexpected error conditions.
That pattern is broken and should not be used, but it’s not incompatible with the general approach of throwing an exception as long as the second |
|
@charmander you’re right. The simplified example I shared above is not correct because it can result in a double release. In practice, the implementation should explicitly handle the error path and ensure the resource is released exactly once, typically by passing the error object to If @brianc is aligned, I can add a throw in this case since it’s behind a feature flag, which should avoid introducing breaking changes for the current version. |
|
The PR is ready for another review. Could you share what the expectations are, and whether it can go into a minor release now that it’s behind a config flag? I’m a first-time contributor, so I’m just trying to understand the process. |
|
@charmander I’ve added native support in b10746f. It depends on brianc/node-libpq#123 |
|
@panga thanks for doing the work here! and for doing the work on the native side too that's top notch! I hate to be a monster about this...but do you think we could separate this out into 1 PR that just adds the transaction status but doesn't add the optional stuff to the pool and then a different one to add the pool behavior? I'm kinda thinking on the pool behavior one what if instead of having a config option that evicts it we add an onRelease: async (client) => {
if (client.getTransactionStatus() !== 'I') {
await client.query('ROLLBACK')
console.error("YO YOUR CODE IS DOING SOMETHING BAD. TRANSACTION AUTOMATICALLY ROLLED BACK")
client.release()
}
}or onRelease: (client) => {
console.error('transaction pending client released. destroying the universe')
process.exit(-1)
}etc... Thoughts? I know its more work...I just want to keep opinions out kinda and extensibility as the focus. Personally I'd probably throw in the release function (and that throw should make the |
Summary
client.getTransactionStatus()method to return the _txStatus valueevictOnOpenTransaction(disabled by default for backwards compatiblity)client.getTransactionStatus()in pg-pool'srelease()method to evict connections not in idle (I) state instead of returning them to the poolBehavior Changes
evictOnOpenTransaction: true, a connection is closed and removed from the pool if it is released with an active transaction (BEGIN without COMMIT/ROLLBACK).Problem
When a client with an active transaction (BEGIN without COMMIT/ROLLBACK) is released back to the pool, the connection is returned in a non-idle transaction state. The next consumer that checks out this connection inherits the open transaction, which can cause:
This is a known as "leaked/poison connection" problem in connection pools. This feature was recently added in Go pgx jackc/pgx#2481
Notes