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

Cancellable Query with AbortSignal option #3212

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

boromisp
Copy link
Contributor

@boromisp boromisp commented May 12, 2024

A suggestion for a new cancellation API (based on the original request in #2774)

This query-specific cancellation API only makes sense because the library waits for each query to complete before it submits the next. With query pipelining it would be a lot messier - or simpler, because the API could offer less guarantees.

While the tests are turned off if AbortController is not available, the library code itself should still be compatible with older node versions.

Example

const r1 = await client.query('SELECT now()')

try {
  await client.query({
    text: 'SELECT pg_sleep(10)',
    signal: AbortSignal.timeout(1_000),
  })
} catch (err) {
  console.log(err.code, err.message) // 57014 canceling statement due to user request
}

const r2 = await client.query(
  'SELECT now() - $1 AS delay',
  [r1.rows[0].now],
)

console.log(r2.rows[0].delay) // PostgresInterval { seconds: 1, milliseconds: 9.592 }

First, in a separate commit (913967f) is a small change that moves the SSL initialization logic to the Connection.
This means the Connection no longer needs a separate an 'sslconnect' event.


Then, the Connection got a new con.cancelWithClone() method that tries to connect to the exact same server, and send a cancel request.

This required a bit of extra state in the Connection, to keep track of the host, port, config and the backendKeyData.

I'm not thrilled with duplicating state from the Client (or with the name of the method for that matter).

What I like about this approach is that all the logic around the cancellation can be implemented in the Submittable and doesn't depend on any special handling by the Client.


Finally the Query got a signal option that can be used to cancel it either before, or after it has been submitted to the server.

If the query has been canceled before submit, it will return the abort reason in submit.

If an actual cancel request has been sent, the query callback and the 'error' and 'end' events are delayed until the cancel request has finished.

This is done to avoid accidentally canceling a different query on the same connection.

If the cancel request isn't completed cleanly, the original connection is destroyed.

This might be triggered if you get an ECONNRESET after sending the cancel request? Honestly I'm not sure.
This should probably treat the errors after the cancel request has been sent differently from errors during connecting.

@charmander
Copy link
Collaborator

This query-specific cancellation API only makes sense because the library waits for each query to complete before it submits the next.

I’m pretty sure there are still unavoidable race conditions that make this not the right API.

@boromisp
Copy link
Contributor Author

I’m pretty sure there are still unavoidable race conditions that make this not the right API.

That was my original impression as well.
But I wanted to write up an implementation, to see the actual problems.

One possible issue I see with this is destroying the connection, if the cancellation connection closes with an error.

This is done, so we don't release the connection with a possible cancellation pending.

However, if the cancellation actually failed, we just left possibly long running query alive on the server. Explicitly sending an RST might be necessary in this case, assuming that is handled similarly as cancel requests by the server.

Even if all this works as I think it does, I'm not so sure if this API is a good idea.


The intent of the code:

  • If you cancel before submitting the query, you skip the query.
  • If you cancel after submitting the query, but before the query is processed, the query should be executed.
  • If you cancel while the query is being executed, the query should be canceled. I'm not so sure about the interaction with the extended query protocol.
  • If you cancel after the query has been executed, nothing should happen.
  • Additionally, if there is an error during cancellation, the connection is destroyed.

So depending on timing the query might be canceled, prevented from being executed or complete successfully.

But the connection should be left In a clean state, or be destroyed.

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

Successfully merging this pull request may close these issues.

None yet

2 participants