1use std::time::Duration;
17
18use crate::protocol::FrontendMessage;
19
20use crate::auth::Codec;
21use crate::config::Config;
22use crate::error::{Error, PgError, Result};
23use crate::transport::{AsyncTransport, SslMode};
24
25#[cfg(feature = "tracing")]
26use crate::tracing_ext::TARGET_CANCEL;
27
28#[derive(Debug, Clone)]
52#[non_exhaustive]
53pub struct CancelToken {
54 pub(crate) host: String,
56 pub(crate) port: u16,
58 pub(crate) process_id: i32,
60 pub(crate) secret_key: i32,
62 pub(crate) ssl_mode: SslMode,
64 pub(crate) accept_invalid_certs: bool,
66}
67
68impl CancelToken {
69 #[cfg(feature = "tls")]
70 fn build_tls_config(&self) -> crate::transport::TlsConfig {
71 crate::transport::TlsConfig::new(self.ssl_mode, &self.host)
72 .accept_invalid_certs(self.accept_invalid_certs)
73 }
74
75 #[must_use = "cancel errors should be checked"]
90 pub async fn cancel(&self) -> Result<()> {
91 self.cancel_with_timeout(None).await
92 }
93
94 #[must_use = "cancel errors should be checked"]
96 pub async fn cancel_with_timeout(&self, timeout: Option<Duration>) -> Result<()> {
97 #[cfg(feature = "tracing")]
98 tracing::debug!(target: TARGET_CANCEL, process_id = self.process_id, "Sending cancel request");
99
100 let mut config = Config::new()
102 .host(&self.host)
103 .port(self.port)
104 .user("cancel"); config = config.ssl_mode(self.ssl_mode);
107 if self.accept_invalid_certs {
108 config = config.accept_invalid_certs(true);
109 }
110
111 let raw_transport = build_cancel_transport(&config, timeout).await?;
113
114 #[cfg(feature = "tls")]
118 let mut transport = if self.ssl_mode != SslMode::Disable {
119 let tls_config = self.build_tls_config();
120 crate::transport::negotiate_tls(raw_transport, &tls_config)
121 .await
122 .map_err(PgError::Transport)?
123 } else {
124 crate::transport::PgTransport::Plain(crate::transport::BufferedTransport::new(
125 raw_transport,
126 ))
127 };
128 #[cfg(not(feature = "tls"))]
129 let mut transport = crate::transport::PgTransport::Plain(
130 crate::transport::BufferedTransport::new(raw_transport),
131 );
132
133 let mut codec = Codec::new();
134
135 codec
137 .send(
138 &mut transport,
139 &FrontendMessage::CancelRequest {
140 process_id: self.process_id,
141 secret_key: self.secret_key,
142 },
143 )
144 .await
145 .map_err(Error::from)?;
146
147 let _ = transport.shutdown().await;
150
151 #[cfg(feature = "tracing")]
152 tracing::info!(target: TARGET_CANCEL, process_id = self.process_id, "Cancel request sent");
153
154 Ok(())
155 }
156
157 pub fn process_id(&self) -> i32 {
159 self.process_id
160 }
161
162 pub fn secret_key(&self) -> i32 {
164 self.secret_key
165 }
166}
167
168#[cfg(target_arch = "wasm32")]
173async fn build_cancel_transport(
174 config: &Config,
175 timeout: Option<Duration>,
176) -> Result<crate::transport::ClientTransport> {
177 use crate::transport::{connect_with_timeout, ClientTransport};
178
179 let tcp = connect_with_timeout(config.get_host(), config.get_port(), timeout)
180 .await
181 .map_err(PgError::Transport)?;
182
183 Ok(ClientTransport::Wasi(tcp))
184}
185
186#[cfg(all(not(target_arch = "wasm32"), feature = "tokio-transport"))]
187async fn build_cancel_transport(
188 config: &Config,
189 timeout: Option<Duration>,
190) -> Result<crate::transport::ClientTransport> {
191 use crate::transport::{connect_with_timeout, ClientTransport};
192
193 let tcp = connect_with_timeout(config.get_host(), config.get_port(), timeout)
194 .await
195 .map_err(PgError::Transport)?;
196
197 Ok(ClientTransport::Tokio(tcp))
198}
199
200#[cfg(all(
201 not(target_arch = "wasm32"),
202 not(feature = "tokio-transport"),
203 feature = "test-native"
204))]
205async fn build_cancel_transport(
206 config: &Config,
207 timeout: Option<Duration>,
208) -> Result<crate::transport::ClientTransport> {
209 use crate::transport::{ClientTransport, NativeTcpTransport};
210
211 let tcp =
212 NativeTcpTransport::connect_with_timeout(config.get_host(), config.get_port(), timeout)
213 .map_err(PgError::Transport)?;
214
215 Ok(ClientTransport::Native(tcp))
216}
217
218#[cfg(all(
219 not(target_arch = "wasm32"),
220 not(feature = "tokio-transport"),
221 not(feature = "test-native")
222))]
223async fn build_cancel_transport(
224 _config: &Config,
225 _timeout: Option<Duration>,
226) -> Result<crate::transport::ClientTransport> {
227 Err(PgError::Unsupported(
228 "no transport available for cancellation. Enable the 'tokio-transport' feature (recommended) or 'test-native' feature, or compile for wasm32-wasip2".into(),
229 ))
230}
231
232#[cfg(test)]
237mod tests {
238 use super::*;
239
240 fn make_token() -> CancelToken {
241 CancelToken {
242 host: "localhost".to_string(),
243 port: 5432,
244 process_id: 12345,
245 secret_key: 67890,
246 ssl_mode: SslMode::Disable,
247 accept_invalid_certs: false,
248 }
249 }
250
251 #[test]
252 fn test_cancel_token_clone() {
253 let token = make_token();
254
255 let cloned = token.clone();
256 assert_eq!(cloned.host, "localhost");
257 assert_eq!(cloned.port, 5432);
258 assert_eq!(cloned.process_id, 12345);
259 assert_eq!(cloned.secret_key, 67890);
260 }
261
262 #[test]
263 fn test_cancel_token_accessors() {
264 let token = CancelToken {
265 process_id: 42,
266 secret_key: 99,
267 ..make_token()
268 };
269
270 assert_eq!(token.process_id(), 42);
271 assert_eq!(token.secret_key(), 99);
272 }
273
274 #[cfg(feature = "tls")]
275 #[test]
276 fn test_cancel_token_tls_config_preserves_hostname_verification() {
277 let token = CancelToken {
278 ssl_mode: SslMode::VerifyFull,
279 accept_invalid_certs: true,
280 ..make_token()
281 };
282
283 let tls_config = token.build_tls_config();
284 assert_eq!(tls_config.mode, SslMode::VerifyFull);
285 assert_eq!(tls_config.server_name, "localhost");
286 assert!(tls_config.accept_invalid_certs);
287 }
288}