Skip to content

Commit a205765

Browse files
authored
fix(postgres): invalidate connection after client-side timeout (#15283)
* fix(postgres): invalidate connection after client-side timeout Merge ff43e8d from main: The `query_timeout` feature of the `pg` package helps handle stuck TCP connections more quickly and gracefully by implementing a client-side timeout: brianc/node-postgres#1713 Sequelize started passing this dialect-specific option through to `pg` here: #13258 I believe we also want to invalidate the connection when a client-side timeout occurs. We shouldn't try to reuse the stuck connection because...it's stuck. This PR updates the error handling code so that the connection is invalidated if the error matches the one thrown from here: https://github.com/brianc/node-postgres/blob/5538df6b446f4b4f921947b460fe38acb897e579/packages/pg/lib/client.js#L529 * fix syntax error * fix another syntax error * fix import
1 parent 67e69cd commit a205765

File tree

2 files changed

+48
-1
lines changed

2 files changed

+48
-1
lines changed

src/dialects/postgres/query.js

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class Query extends AbstractQuery {
8585
|| /Unable to set non-blocking to true/i.test(error)
8686
|| /SSL SYSCALL error: EOF detected/i.test(error)
8787
|| /Local: Authentication failure/i.test(error)
88+
// https://github.com/sequelize/sequelize/pull/15144
89+
|| error.message === 'Query read timeout'
8890
) {
8991
connection._invalid = true;
9092
}

test/integration/dialects/postgres/query.test.js

+46-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const chai = require('chai'),
44
expect = chai.expect,
55
Support = require('../../support'),
66
dialect = Support.getTestDialect(),
7-
DataTypes = require('sequelize/lib/data-types');
7+
DataTypes = require('sequelize/lib/data-types'),
8+
DatabaseError = require('sequelize/lib/errors/database-error');
89

910
if (dialect.match(/^postgres/)) {
1011
describe('[POSTGRES] Query', () => {
@@ -188,5 +189,49 @@ if (dialect.match(/^postgres/)) {
188189
order: [['order_0', 'DESC']]
189190
});
190191
});
192+
193+
describe('Connection Invalidation', () => {
194+
if (process.env.DIALECT === 'postgres-native') {
195+
// native driver doesn't support statement_timeout or query_timeout
196+
return;
197+
}
198+
199+
async function setUp(clientQueryTimeoutMs) {
200+
const sequelize = Support.createSequelizeInstance({
201+
dialectOptions: {
202+
statement_timeout: 500, // ms
203+
query_timeout: clientQueryTimeoutMs
204+
},
205+
pool: {
206+
max: 1, // having only one helps us know whether the connection was invalidated
207+
idle: 60000
208+
}
209+
});
210+
211+
return { sequelize, originalPid: await getConnectionPid(sequelize) };
212+
}
213+
214+
async function getConnectionPid(sequelize) {
215+
const connection = await sequelize.connectionManager.getConnection();
216+
const pid = connection.processID;
217+
sequelize.connectionManager.releaseConnection(connection);
218+
219+
return pid;
220+
}
221+
222+
it('reuses connection after statement timeout', async () => {
223+
// client timeout > statement timeout means that the query should fail with a statement timeout
224+
const { sequelize, originalPid } = await setUp(10000);
225+
await expect(sequelize.query('select pg_sleep(1)')).to.eventually.be.rejectedWith(DatabaseError, 'canceling statement due to statement timeout');
226+
expect(await getConnectionPid(sequelize)).to.equal(originalPid);
227+
});
228+
229+
it('invalidates connection after client-side query timeout', async () => {
230+
// client timeout < statement timeout means that the query should fail with a read timeout
231+
const { sequelize, originalPid } = await setUp(250);
232+
await expect(sequelize.query('select pg_sleep(1)')).to.eventually.be.rejectedWith(DatabaseError, 'Query read timeout');
233+
expect(await getConnectionPid(sequelize)).to.not.equal(originalPid);
234+
});
235+
});
191236
});
192237
}

0 commit comments

Comments
 (0)