Skip to main content

hyperdb_api_core/client/
cancel.rs

1// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Query cancellation abstraction.
5//!
6//! Hyper supports two transport flavors — the `PostgreSQL` wire protocol over
7//! TCP / domain sockets / named pipes, and gRPC over HTTP/2 (used by
8//! Salesforce Data 360). Each transport has its own idiomatic way to signal
9//! "cancel the query currently running on this connection":
10//!
11//! | Transport | Cancel mechanism |
12//! |-----------|------------------|
13//! | PG wire   | Open a *separate* connection to the same endpoint and send a pre-auth `CancelRequest` packet (process id + secret key). The server recognizes it and signals the in-flight query on the original connection to abort. |
14//! | gRPC      | Send a `cancel_query(query_id)` RPC over the *same* HTTP/2 channel (HTTP/2 natively multiplexes streams). |
15//!
16//! Both boil down to a "fire-and-forget best-effort cancel" from the
17//! caller's point of view, so we expose them through the [`Cancellable`]
18//! trait. Consumers (e.g. [`super::client::QueryStream`]'s `Drop` impl)
19//! take a `&dyn Cancellable` and don't care which transport is underneath.
20//!
21//! Cancellation is inherently racy: the server may have already emitted
22//! the last `DataRow` before the cancel lands, the query may complete
23//! normally before the cancel is processed, or the network may drop the
24//! cancel packet entirely. All implementations therefore treat cancel as
25//! best-effort: errors are logged via `tracing::warn!` and then swallowed,
26//! because callers are typically in destructor or error-cleanup paths
27//! where propagating an error is inappropriate.
28
29/// Anything that can signal the server to abort the query currently running
30/// on an associated connection.
31///
32/// Implementations must be `Send + Sync` so that streaming result types can
33/// hold a `&dyn Cancellable` across await points and thread boundaries.
34///
35/// # Guarantees
36///
37/// - `cancel()` is **fire-and-forget**. It does not block waiting for the
38///   server to acknowledge the cancel or for the in-flight query to
39///   actually stop. Callers that need to observe the query's final state
40///   should drain the original connection (see
41///   [`super::connection::RawConnection::drain_until_ready_bounded`]).
42/// - `cancel()` **never panics and never returns an error**. Any
43///   transport-level failures (e.g. the cancel connection can't be opened)
44///   are logged and swallowed. This keeps `cancel()` usable from `Drop`
45///   impls, which cannot propagate errors.
46/// - `cancel()` is **idempotent and late-safe**. It is always possible
47///   for the in-flight query to complete between the moment a caller
48///   decides to cancel and the moment the cancel actually reaches the
49///   server — the query's natural `ReadyForQuery` and the cancel
50///   request race. Both PG wire and gRPC handle this correctly: the
51///   PG wire `CancelRequest` travels on a *separate* connection and
52///   only affects the query currently bound to the target connection's
53///   process id, so a cancel that arrives after the query finished
54///   targets nothing and is a harmless no-op; the gRPC `cancel_query`
55///   RPC is similarly keyed on a server-assigned query id and
56///   returns gracefully when the id corresponds to a completed query.
57///   Connection-pool state is never affected by a late cancel,
58///   because cancels never mutate the underlying connection's
59///   protocol state — they only signal the server to abort work on
60///   an already-separately-tracked query.
61///
62/// # Writing an implementation
63///
64/// `Cancellable` is an **internal cleanup abstraction**, not a user-facing
65/// cancel API. Most transports already have a natural
66/// `pub fn cancel_...(..) -> Result<(), TransportError>` method that users
67/// call directly when they want error-aware cancellation (metrics, retry,
68/// user feedback, etc). A `Cancellable` impl is a thin wrapper around that
69/// fallible API that swallows transport errors (logged via
70/// `tracing::warn!`) so the trait method can satisfy its no-error
71/// guarantee.
72///
73/// The canonical example is
74/// [`impl Cancellable for super::client::Client`](super::client::Client),
75/// which wraps the fallible
76/// [`Client::cancel`](super::client::Client::cancel) PG wire
77/// `CancelRequest` method:
78///
79/// ```ignore
80/// impl Cancellable for Client {
81///     fn cancel(&self) {
82///         if let Err(e) = Client::cancel(self) {
83///             tracing::warn!(error = %e, "cancel failed (swallowed)");
84///         }
85///     }
86/// }
87/// ```
88///
89/// The gRPC transport has the same fallible user API
90/// ([`GrpcClient::cancel_query`](super::grpc::GrpcClient::cancel_query))
91/// but **no `Cancellable` impl today** — gRPC has no streaming result type
92/// whose `Drop` would consume `&dyn Cancellable`, and `Cancellable::cancel`
93/// takes no arguments so it cannot be implemented directly on `GrpcClient`
94/// (which doesn't know *which* `query_id` to cancel). A future gRPC
95/// streaming type will introduce a per-query handle along the lines of
96/// `GrpcCancelHandle { client, query_id }` that implements `Cancellable`
97/// by wrapping and swallowing `GrpcClient::cancel_query`.
98pub trait Cancellable: Send + Sync {
99    /// Send a best-effort cancel signal for the query currently in flight
100    /// on the associated connection.
101    fn cancel(&self);
102}