Description
Is there any way to force close the database connection in a pool, even if there are queries running? The use case is to force close a connection obtained during an HTTP request in case of a timeout managed from the routes/middlewares layer in the HTTP server.
We've recently encountered with an issue on our server which made the database fill up the available connections because HTTP was timing out, but the database was still processing queries, even after the HTTP socket was closed.
We were able to achieve what we want with some tweaks in the postgres package, but I would like to hear your opinion in case you feel an alternative API would be better. Ideally without requiring extending a Pool implementation
Similar previous work:
grafana/grafana#22123
Postgres Dart fork with changes: https://github.com/davidmartos96/postgresql-dart/tree/feat/abort_conn
Custom pool, but it extends the PoolImplementation
/// A pool that aborts the connection if receives an abort signal.
/// This is useful to abort the db query if the request is aborted.
class AbortSignalAwarePool<T> extends pool_impl.PoolImplementation<T> {
AbortSignalAwarePool(super.selector, super.settings);
factory AbortSignalAwarePool.withEndpoints(
List<pg.Endpoint> endpoints, {
pg.PoolSettings? settings,
}) =>
AbortSignalAwarePool(pool_impl.roundRobinSelector(endpoints), settings);
/// This method is the only one that is overridden to add the abort signal handling.
/// and let the postgres library know about the error outside the connection callback
/// so that it releases the connection.
@override
Future<R> withConnection<R>(
Future<R> Function(pg.Connection connection) fn, {
pg.ConnectionSettings? settings,
locality,
}) {
// AbortSignal internally it's a Completer that can only be completed with an error
// There can be an AbortSignal in the current Dart zone if we pass it through
// in an appropriate HTTP middleware
final AbortSignal? abortSignal = getAbortSignalInZone();
final Future<R> Function(pg.Connection connection) effectiveFn;
if (abortSignal == null) {
effectiveFn = fn;
} else {
effectiveFn = (connection) async {
try {
// Internally it's a Future.any([abortSignal, fn(connection)])
// so that the connection callbacks throws if the abort signal is triggered
return await abortSignal.waitFuture(fn(connection));
} catch (e) {
if (abortSignal.wasAborted) {
// Interrupt the pg connection if the http request has finished
// to avoid dangling connections
unawaited(connection.close(interruptRunning: true));
}
rethrow;
}
};
}
return super.withConnection(effectiveFn, settings: settings, locality: locality);
}
}