Support cancellation in synchronous client
This commit is contained in:
parent
a8fa2a42c0
commit
33dbbcbee7
36
postgres/src/cancel_token.rs
Normal file
36
postgres/src/cancel_token.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use tokio::runtime;
|
||||||
|
use tokio_postgres::tls::MakeTlsConnect;
|
||||||
|
use tokio_postgres::{Error, Socket};
|
||||||
|
|
||||||
|
/// The capability to request cancellation of in-progress queries on a
|
||||||
|
/// connection.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CancelToken(tokio_postgres::CancelToken);
|
||||||
|
|
||||||
|
impl CancelToken {
|
||||||
|
pub(crate) fn new(inner: tokio_postgres::CancelToken) -> CancelToken {
|
||||||
|
CancelToken(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to cancel the in-progress query on the connection associated
|
||||||
|
/// with this `CancelToken`.
|
||||||
|
///
|
||||||
|
/// The server provides no information about whether a cancellation attempt was successful or not. An error will
|
||||||
|
/// only be returned if the client was unable to connect to the database.
|
||||||
|
///
|
||||||
|
/// Cancellation is inherently racy. There is no guarantee that the
|
||||||
|
/// cancellation request will reach the server before the query terminates
|
||||||
|
/// normally, or that the connection associated with this token is still
|
||||||
|
/// active.
|
||||||
|
pub fn cancel_query<T>(&self, tls: T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: MakeTlsConnect<Socket>,
|
||||||
|
{
|
||||||
|
runtime::Builder::new()
|
||||||
|
.enable_all()
|
||||||
|
.basic_scheduler()
|
||||||
|
.build()
|
||||||
|
.unwrap() // FIXME don't unwrap
|
||||||
|
.block_on(self.0.cancel_query(tls))
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
use crate::{Config, CopyInWriter, CopyOutReader, RowIter, Statement, ToStatement, Transaction};
|
use crate::{
|
||||||
|
CancelToken, Config, CopyInWriter, CopyOutReader, RowIter, Statement, ToStatement, Transaction,
|
||||||
|
};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio_postgres::tls::{MakeTlsConnect, TlsConnect};
|
use tokio_postgres::tls::{MakeTlsConnect, TlsConnect};
|
||||||
@ -443,6 +445,46 @@ impl Client {
|
|||||||
Ok(Transaction::new(&mut self.runtime, transaction))
|
Ok(Transaction::new(&mut self.runtime, transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a cancellation token that can later be used to request
|
||||||
|
/// cancellation of a query running on this connection.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use postgres::{Client, NoTls};
|
||||||
|
/// use postgres::error::SqlState;
|
||||||
|
/// use std::thread;
|
||||||
|
/// use std::time::Duration;
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut client = Client::connect("host=localhost user=postgres", NoTls)?;
|
||||||
|
///
|
||||||
|
/// let cancel_token = client.cancel_token();
|
||||||
|
///
|
||||||
|
/// thread::spawn(move || {
|
||||||
|
/// // Abort the query after 5s.
|
||||||
|
/// thread::sleep(Duration::from_secs(5));
|
||||||
|
/// cancel_token.cancel_query(NoTls);
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// match client.simple_query("SELECT long_running_query()") {
|
||||||
|
/// Err(e) if e.code() == Some(&SqlState::QUERY_CANCELED) => {
|
||||||
|
/// // Handle canceled query.
|
||||||
|
/// }
|
||||||
|
/// Err(err) => return Err(err.into()),
|
||||||
|
/// Ok(rows) => {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// // ...
|
||||||
|
///
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn cancel_token(&self) -> CancelToken {
|
||||||
|
CancelToken::new(self.client.cancel_token())
|
||||||
|
}
|
||||||
|
|
||||||
/// Determines if the client's connection has already closed.
|
/// Determines if the client's connection has already closed.
|
||||||
///
|
///
|
||||||
/// If this returns `true`, the client is no longer usable.
|
/// If this returns `true`, the client is no longer usable.
|
||||||
|
@ -54,6 +54,7 @@ pub use tokio_postgres::{
|
|||||||
error, row, tls, types, Column, Portal, SimpleQueryMessage, Socket, Statement, ToStatement,
|
error, row, tls, types, Column, Portal, SimpleQueryMessage, Socket, Statement, ToStatement,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use crate::cancel_token::CancelToken;
|
||||||
pub use crate::client::*;
|
pub use crate::client::*;
|
||||||
pub use crate::config::Config;
|
pub use crate::config::Config;
|
||||||
pub use crate::copy_in_writer::CopyInWriter;
|
pub use crate::copy_in_writer::CopyInWriter;
|
||||||
@ -68,6 +69,7 @@ pub use crate::tls::NoTls;
|
|||||||
pub use crate::transaction::*;
|
pub use crate::transaction::*;
|
||||||
|
|
||||||
pub mod binary_copy;
|
pub mod binary_copy;
|
||||||
|
mod cancel_token;
|
||||||
mod client;
|
mod client;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod copy_in_writer;
|
mod copy_in_writer;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio_postgres::error::SqlState;
|
||||||
use tokio_postgres::types::Type;
|
use tokio_postgres::types::Type;
|
||||||
use tokio_postgres::NoTls;
|
use tokio_postgres::NoTls;
|
||||||
|
|
||||||
@ -288,3 +291,21 @@ fn portal() {
|
|||||||
assert_eq!(rows.len(), 1);
|
assert_eq!(rows.len(), 1);
|
||||||
assert_eq!(rows[0].get::<_, i32>(0), 3);
|
assert_eq!(rows[0].get::<_, i32>(0), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cancel_query() {
|
||||||
|
let mut client = Client::connect("host=localhost port=5433 user=postgres", NoTls).unwrap();
|
||||||
|
|
||||||
|
let cancel_token = client.cancel_token();
|
||||||
|
let cancel_thread = thread::spawn(move || {
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
cancel_token.cancel_query(NoTls).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
match client.batch_execute("SELECT pg_sleep(100)") {
|
||||||
|
Err(e) if e.code() == Some(&SqlState::QUERY_CANCELED) => {}
|
||||||
|
t => panic!("unexpected return: {:?}", t),
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel_thread.join().unwrap();
|
||||||
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use crate::{CopyInWriter, CopyOutReader, Portal, RowIter, Rt, Statement, ToStatement};
|
use crate::{
|
||||||
|
CancelToken, CopyInWriter, CopyOutReader, Portal, RowIter, Rt, Statement, ToStatement,
|
||||||
|
};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio_postgres::types::{ToSql, Type};
|
use tokio_postgres::types::{ToSql, Type};
|
||||||
use tokio_postgres::{Error, Row, SimpleQueryMessage};
|
use tokio_postgres::{Error, Row, SimpleQueryMessage};
|
||||||
@ -168,6 +170,11 @@ impl<'a> Transaction<'a> {
|
|||||||
self.runtime.block_on(self.transaction.batch_execute(query))
|
self.runtime.block_on(self.transaction.batch_execute(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like `Client::cancel_token`.
|
||||||
|
pub fn cancel_token(&self) -> CancelToken {
|
||||||
|
CancelToken::new(self.transaction.cancel_token())
|
||||||
|
}
|
||||||
|
|
||||||
/// Like `Client::transaction`.
|
/// Like `Client::transaction`.
|
||||||
pub fn transaction(&mut self) -> Result<Transaction<'_>, Error> {
|
pub fn transaction(&mut self) -> Result<Transaction<'_>, Error> {
|
||||||
let transaction = self.runtime.block_on(self.transaction.transaction())?;
|
let transaction = self.runtime.block_on(self.transaction.transaction())?;
|
||||||
|
63
tokio-postgres/src/cancel_token.rs
Normal file
63
tokio-postgres/src/cancel_token.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use crate::config::SslMode;
|
||||||
|
use crate::tls::TlsConnect;
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use crate::{cancel_query, client::SocketConfig, tls::MakeTlsConnect, Socket};
|
||||||
|
use crate::{cancel_query_raw, Error};
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
/// The capability to request cancellation of in-progress queries on a
|
||||||
|
/// connection.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CancelToken {
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(crate) socket_config: Option<SocketConfig>,
|
||||||
|
pub(crate) ssl_mode: SslMode,
|
||||||
|
pub(crate) process_id: i32,
|
||||||
|
pub(crate) secret_key: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CancelToken {
|
||||||
|
/// Attempts to cancel the in-progress query on the connection associated
|
||||||
|
/// with this `CancelToken`.
|
||||||
|
///
|
||||||
|
/// The server provides no information about whether a cancellation attempt was successful or not. An error will
|
||||||
|
/// only be returned if the client was unable to connect to the database.
|
||||||
|
///
|
||||||
|
/// Cancellation is inherently racy. There is no guarantee that the
|
||||||
|
/// cancellation request will reach the server before the query terminates
|
||||||
|
/// normally, or that the connection associated with this token is still
|
||||||
|
/// active.
|
||||||
|
///
|
||||||
|
/// Requires the `runtime` Cargo feature (enabled by default).
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub async fn cancel_query<T>(&self, tls: T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: MakeTlsConnect<Socket>,
|
||||||
|
{
|
||||||
|
cancel_query::cancel_query(
|
||||||
|
self.socket_config.clone(),
|
||||||
|
self.ssl_mode,
|
||||||
|
tls,
|
||||||
|
self.process_id,
|
||||||
|
self.secret_key,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `cancel_query`, but uses a stream which is already connected to the server rather than opening a new
|
||||||
|
/// connection itself.
|
||||||
|
pub async fn cancel_query_raw<S, T>(&self, stream: S, tls: T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
S: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
T: TlsConnect<S>,
|
||||||
|
{
|
||||||
|
cancel_query_raw::cancel_query_raw(
|
||||||
|
stream,
|
||||||
|
self.ssl_mode,
|
||||||
|
tls,
|
||||||
|
self.process_id,
|
||||||
|
self.secret_key,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
#[cfg(feature = "runtime")]
|
|
||||||
use crate::cancel_query;
|
|
||||||
use crate::codec::BackendMessages;
|
use crate::codec::BackendMessages;
|
||||||
use crate::config::{Host, SslMode};
|
use crate::config::{Host, SslMode};
|
||||||
use crate::connection::{Request, RequestMessages};
|
use crate::connection::{Request, RequestMessages};
|
||||||
@ -14,7 +12,7 @@ use crate::to_statement::ToStatement;
|
|||||||
use crate::types::{Oid, ToSql, Type};
|
use crate::types::{Oid, ToSql, Type};
|
||||||
#[cfg(feature = "runtime")]
|
#[cfg(feature = "runtime")]
|
||||||
use crate::Socket;
|
use crate::Socket;
|
||||||
use crate::{cancel_query_raw, copy_in, copy_out, query, CopyInSink, Transaction};
|
use crate::{copy_in, copy_out, query, CancelToken, CopyInSink, Transaction};
|
||||||
use crate::{prepare, SimpleQueryMessage};
|
use crate::{prepare, SimpleQueryMessage};
|
||||||
use crate::{simple_query, Row};
|
use crate::{simple_query, Row};
|
||||||
use crate::{Error, Statement};
|
use crate::{Error, Statement};
|
||||||
@ -451,6 +449,19 @@ impl Client {
|
|||||||
Ok(Transaction::new(self))
|
Ok(Transaction::new(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a cancellation token that can later be used to request
|
||||||
|
/// cancellation of a query running on the connection associated with
|
||||||
|
/// this client.
|
||||||
|
pub fn cancel_token(&self) -> CancelToken {
|
||||||
|
CancelToken {
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
socket_config: self.socket_config.clone(),
|
||||||
|
ssl_mode: self.ssl_mode,
|
||||||
|
process_id: self.process_id,
|
||||||
|
secret_key: self.secret_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to cancel an in-progress query.
|
/// Attempts to cancel an in-progress query.
|
||||||
///
|
///
|
||||||
/// The server provides no information about whether a cancellation attempt was successful or not. An error will
|
/// The server provides no information about whether a cancellation attempt was successful or not. An error will
|
||||||
@ -458,35 +469,23 @@ impl Client {
|
|||||||
///
|
///
|
||||||
/// Requires the `runtime` Cargo feature (enabled by default).
|
/// Requires the `runtime` Cargo feature (enabled by default).
|
||||||
#[cfg(feature = "runtime")]
|
#[cfg(feature = "runtime")]
|
||||||
|
#[deprecated(since = "0.6.0", note = "use Client::cancel_token() instead")]
|
||||||
pub async fn cancel_query<T>(&self, tls: T) -> Result<(), Error>
|
pub async fn cancel_query<T>(&self, tls: T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: MakeTlsConnect<Socket>,
|
T: MakeTlsConnect<Socket>,
|
||||||
{
|
{
|
||||||
cancel_query::cancel_query(
|
self.cancel_token().cancel_query(tls).await
|
||||||
self.socket_config.clone(),
|
|
||||||
self.ssl_mode,
|
|
||||||
tls,
|
|
||||||
self.process_id,
|
|
||||||
self.secret_key,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like `cancel_query`, but uses a stream which is already connected to the server rather than opening a new
|
/// Like `cancel_query`, but uses a stream which is already connected to the server rather than opening a new
|
||||||
/// connection itself.
|
/// connection itself.
|
||||||
|
#[deprecated(since = "0.6.0", note = "use Client::cancel_token() instead")]
|
||||||
pub async fn cancel_query_raw<S, T>(&self, stream: S, tls: T) -> Result<(), Error>
|
pub async fn cancel_query_raw<S, T>(&self, stream: S, tls: T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
S: AsyncRead + AsyncWrite + Unpin,
|
S: AsyncRead + AsyncWrite + Unpin,
|
||||||
T: TlsConnect<S>,
|
T: TlsConnect<S>,
|
||||||
{
|
{
|
||||||
cancel_query_raw::cancel_query_raw(
|
self.cancel_token().cancel_query_raw(stream, tls).await
|
||||||
stream,
|
|
||||||
self.ssl_mode,
|
|
||||||
tls,
|
|
||||||
self.process_id,
|
|
||||||
self.secret_key,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines if the connection to the server has already closed.
|
/// Determines if the connection to the server has already closed.
|
||||||
|
@ -99,6 +99,7 @@
|
|||||||
#![doc(html_root_url = "https://docs.rs/tokio-postgres/0.5")]
|
#![doc(html_root_url = "https://docs.rs/tokio-postgres/0.5")]
|
||||||
#![warn(rust_2018_idioms, clippy::all, missing_docs)]
|
#![warn(rust_2018_idioms, clippy::all, missing_docs)]
|
||||||
|
|
||||||
|
pub use crate::cancel_token::CancelToken;
|
||||||
pub use crate::client::Client;
|
pub use crate::client::Client;
|
||||||
pub use crate::config::Config;
|
pub use crate::config::Config;
|
||||||
pub use crate::connection::Connection;
|
pub use crate::connection::Connection;
|
||||||
@ -125,6 +126,7 @@ mod bind;
|
|||||||
#[cfg(feature = "runtime")]
|
#[cfg(feature = "runtime")]
|
||||||
mod cancel_query;
|
mod cancel_query;
|
||||||
mod cancel_query_raw;
|
mod cancel_query_raw;
|
||||||
|
mod cancel_token;
|
||||||
mod client;
|
mod client;
|
||||||
mod codec;
|
mod codec;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
@ -9,8 +9,8 @@ use crate::types::{ToSql, Type};
|
|||||||
#[cfg(feature = "runtime")]
|
#[cfg(feature = "runtime")]
|
||||||
use crate::Socket;
|
use crate::Socket;
|
||||||
use crate::{
|
use crate::{
|
||||||
bind, query, slice_iter, Client, CopyInSink, Error, Portal, Row, SimpleQueryMessage, Statement,
|
bind, query, slice_iter, CancelToken, Client, CopyInSink, Error, Portal, Row,
|
||||||
ToStatement,
|
SimpleQueryMessage, Statement, ToStatement,
|
||||||
};
|
};
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
@ -249,21 +249,30 @@ impl<'a> Transaction<'a> {
|
|||||||
self.client.batch_execute(query).await
|
self.client.batch_execute(query).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like `Client::cancel_token`.
|
||||||
|
pub fn cancel_token(&self) -> CancelToken {
|
||||||
|
self.client.cancel_token()
|
||||||
|
}
|
||||||
|
|
||||||
/// Like `Client::cancel_query`.
|
/// Like `Client::cancel_query`.
|
||||||
#[cfg(feature = "runtime")]
|
#[cfg(feature = "runtime")]
|
||||||
|
#[deprecated(since = "0.6.0", note = "use Transaction::cancel_token() instead")]
|
||||||
pub async fn cancel_query<T>(&self, tls: T) -> Result<(), Error>
|
pub async fn cancel_query<T>(&self, tls: T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: MakeTlsConnect<Socket>,
|
T: MakeTlsConnect<Socket>,
|
||||||
{
|
{
|
||||||
|
#[allow(deprecated)]
|
||||||
self.client.cancel_query(tls).await
|
self.client.cancel_query(tls).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like `Client::cancel_query_raw`.
|
/// Like `Client::cancel_query_raw`.
|
||||||
|
#[deprecated(since = "0.6.0", note = "use Transaction::cancel_token() instead")]
|
||||||
pub async fn cancel_query_raw<S, T>(&self, stream: S, tls: T) -> Result<(), Error>
|
pub async fn cancel_query_raw<S, T>(&self, stream: S, tls: T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
S: AsyncRead + AsyncWrite + Unpin,
|
S: AsyncRead + AsyncWrite + Unpin,
|
||||||
T: TlsConnect<S>,
|
T: TlsConnect<S>,
|
||||||
{
|
{
|
||||||
|
#[allow(deprecated)]
|
||||||
self.client.cancel_query_raw(stream, tls).await
|
self.client.cancel_query_raw(stream, tls).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +304,8 @@ async fn cancel_query_raw() {
|
|||||||
let client = connect("user=postgres").await;
|
let client = connect("user=postgres").await;
|
||||||
|
|
||||||
let socket = TcpStream::connect("127.0.0.1:5433").await.unwrap();
|
let socket = TcpStream::connect("127.0.0.1:5433").await.unwrap();
|
||||||
let cancel = client.cancel_query_raw(socket, NoTls);
|
let cancel_token = client.cancel_token();
|
||||||
|
let cancel = cancel_token.cancel_query_raw(socket, NoTls);
|
||||||
let cancel = time::delay_for(Duration::from_millis(100)).then(|()| cancel);
|
let cancel = time::delay_for(Duration::from_millis(100)).then(|()| cancel);
|
||||||
|
|
||||||
let sleep = client.batch_execute("SELECT pg_sleep(100)");
|
let sleep = client.batch_execute("SELECT pg_sleep(100)");
|
||||||
|
@ -70,7 +70,8 @@ async fn target_session_attrs_err() {
|
|||||||
async fn cancel_query() {
|
async fn cancel_query() {
|
||||||
let client = connect("host=localhost port=5433 user=postgres").await;
|
let client = connect("host=localhost port=5433 user=postgres").await;
|
||||||
|
|
||||||
let cancel = client.cancel_query(NoTls);
|
let cancel_token = client.cancel_token();
|
||||||
|
let cancel = cancel_token.cancel_query(NoTls);
|
||||||
let cancel = time::delay_for(Duration::from_millis(100)).then(|()| cancel);
|
let cancel = time::delay_for(Duration::from_millis(100)).then(|()| cancel);
|
||||||
|
|
||||||
let sleep = client.batch_execute("SELECT pg_sleep(100)");
|
let sleep = client.batch_execute("SELECT pg_sleep(100)");
|
||||||
|
Loading…
Reference in New Issue
Block a user