Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix timeout method #4324

Merged
merged 5 commits into from Mar 21, 2021
Merged

Fix timeout method #4324

merged 5 commits into from Mar 21, 2021

Conversation

martinmacko47
Copy link
Contributor

Based on PR #2527 - rebased and added the fix for postgres too:

KILL commands have to be sent on a seprate connection, and transaction Clients override acquireConnection to always return the same connection.

Furthermore, it is plausible that some applications may never be able to Acquire a pooled connection for cancellation becuase a query which is Exceeding the timeout may coincidentally exhaust the connection pool.

For example, if a DDL operation is blocking writes, a connection pool May be quickly exhaused by write operations.

Timeout cancellation during transaction is fixed by using acquireRawConnection and _query to bypass the transaction connection and cancel the query from a separate connection.

fixes #2211

@@ -869,7 +869,118 @@ module.exports = function (knex) {
});
});

it('.timeout(ms, {cancel: true}) should throw error if cancellation cannot acquire connection', async function () {
it('.timeout(ms, {cancel: true}) should throw TimeoutError and cancel slow query in transaction', function () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is a copy of ".timeout(ms, {cancel: true}) should throw TimeoutError and cancel slow query" test with just the query enveloped into a transaction.

You can use this test to confirm that canceling a timeouted query does not work inside a transaction on current master. The test will fail there. I've made as little as possible changes to the test, to make sure the cause is the transaction.

});
});

it.skip('.timeout(ms, {cancel: true}) should throw error if cancellation cannot acquire connection', async function () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We go around the connection pool for the cancellation query by purpose. As the pool will likely to be exhausted when queries start timeouting, which would prevent us from cancelling the query in the first place.

Therefore this test is contradictory and will fail, as an exhausted pool should not prevent the cancellation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the test then

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we ignore the pool limits for all dialects now, or test still makes sense for some of them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kibertoad

Do we ignore the pool limits for all dialects now, or test still makes sense for some of them?

Query cancelling is supported ony for mysql and postgres, so this test was skipped for other dialects anyway:

      // Only mysql/postgres query cancelling supported for now
      if (!isMysql(knex) && !isPostgreSQL(knex)) {
        return this.skip();
      }

We can remove the test then

Maybe I'd rather turn the test arround that the query is indeed cancelled even if pool is exhausted.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kibertoad Test refactored in 944e868

try {
return await this.query(conn, {
return await this._query(conn, {
method: 'raw',
sql: 'KILL QUERY ?',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bit of semantic difference between MySQL and Postgress:

  • KILL QUERY ? in MySQL stops just the current query, but keeps the transaction active. So the user can send further queries to the same transaction and commit it eventually.
  • SELECT pg_cancel_backend($1) in Postress stops the current query, and also automaticaly rejects the transaction. So any further queries sent to the same transaction will fail and the transaction will be eventually rolled back.

Should we keep this semantinc difference? Or should we rather manually roll back the transaction in MySQL as well. If we decide either way, it won't be a breaking change, as the query cancellation didn't work in transactions so far anyway.

@kibertoad
Copy link
Collaborator

@elhigu @maximelkin @briandamaged This needs a careful review.

@martinmacko47
Copy link
Contributor Author

The CI error seems unrelated. When I run the worflow on my fork, CI passed. Perhaps just hit the re-run button (I don't have access to it, so I can't re-run the workflow for the PR).

@kibertoad kibertoad merged commit 1744c8c into knex:master Mar 21, 2021
@kibertoad
Copy link
Collaborator

Released in 0.95.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

.timeout(ms, {cancel: true}) not working for transactions
3 participants