mirror of
https://github.com/launchbadge/sqlx
synced 2024-11-12 23:37:13 +00:00
parent
09717e1b83
commit
f38c739f7f
1 changed files with 106 additions and 9 deletions
|
@ -55,13 +55,13 @@ enum Command {
|
||||||
tx: flume::Sender<Result<Either<SqliteQueryResult, SqliteRow>, Error>>,
|
tx: flume::Sender<Result<Either<SqliteQueryResult, SqliteRow>, Error>>,
|
||||||
},
|
},
|
||||||
Begin {
|
Begin {
|
||||||
tx: oneshot::Sender<Result<(), Error>>,
|
tx: rendezvous_oneshot::Sender<Result<(), Error>>,
|
||||||
},
|
},
|
||||||
Commit {
|
Commit {
|
||||||
tx: oneshot::Sender<Result<(), Error>>,
|
tx: rendezvous_oneshot::Sender<Result<(), Error>>,
|
||||||
},
|
},
|
||||||
Rollback {
|
Rollback {
|
||||||
tx: Option<oneshot::Sender<Result<(), Error>>>,
|
tx: Option<rendezvous_oneshot::Sender<Result<(), Error>>>,
|
||||||
},
|
},
|
||||||
CreateCollation {
|
CreateCollation {
|
||||||
create_collation:
|
create_collation:
|
||||||
|
@ -116,6 +116,11 @@ impl ConnectionWorker {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If COMMIT or ROLLBACK is processed but not acknowledged, there would be another
|
||||||
|
// ROLLBACK sent when the `Transaction` drops. We need to ignore it otherwise we
|
||||||
|
// would rollback an already completed transaction.
|
||||||
|
let mut ignore_next_start_rollback = false;
|
||||||
|
|
||||||
for cmd in command_rx {
|
for cmd in command_rx {
|
||||||
match cmd {
|
match cmd {
|
||||||
Command::Prepare { query, tx } => {
|
Command::Prepare { query, tx } => {
|
||||||
|
@ -162,8 +167,27 @@ impl ConnectionWorker {
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
conn.transaction_depth += 1;
|
conn.transaction_depth += 1;
|
||||||
});
|
});
|
||||||
|
let res_ok = res.is_ok();
|
||||||
|
|
||||||
tx.send(res).ok();
|
if tx.blocking_send(res).is_err() && res_ok {
|
||||||
|
// The BEGIN was processed but not acknowledged. This means no
|
||||||
|
// `Transaction` was created and so there is no way to commit /
|
||||||
|
// rollback this transaction. We need to roll it back
|
||||||
|
// immediately otherwise it would remain started forever.
|
||||||
|
if let Err(e) = conn
|
||||||
|
.handle
|
||||||
|
.exec(rollback_ansi_transaction_sql(depth + 1))
|
||||||
|
.map(|_| {
|
||||||
|
conn.transaction_depth -= 1;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
// The rollback failed. To prevent leaving the connection
|
||||||
|
// in an inconsistent state we shutdown this worker which
|
||||||
|
// causes any subsequent operation on the connection to fail.
|
||||||
|
log::error!("failed to rollback cancelled transaction: {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command::Commit { tx } => {
|
Command::Commit { tx } => {
|
||||||
let depth = conn.transaction_depth;
|
let depth = conn.transaction_depth;
|
||||||
|
@ -177,10 +201,21 @@ impl ConnectionWorker {
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
let res_ok = res.is_ok();
|
||||||
|
|
||||||
tx.send(res).ok();
|
if tx.blocking_send(res).is_err() && res_ok {
|
||||||
|
// The COMMIT was processed but not acknowledged. This means that
|
||||||
|
// the `Transaction` doesn't know it was committed and will try to
|
||||||
|
// rollback on drop. We need to ignore that rollback.
|
||||||
|
ignore_next_start_rollback = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command::Rollback { tx } => {
|
Command::Rollback { tx } => {
|
||||||
|
if ignore_next_start_rollback && tx.is_none() {
|
||||||
|
ignore_next_start_rollback = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let depth = conn.transaction_depth;
|
let depth = conn.transaction_depth;
|
||||||
|
|
||||||
let res = if depth > 0 {
|
let res = if depth > 0 {
|
||||||
|
@ -193,8 +228,16 @@ impl ConnectionWorker {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let res_ok = res.is_ok();
|
||||||
|
|
||||||
if let Some(tx) = tx {
|
if let Some(tx) = tx {
|
||||||
tx.send(res).ok();
|
if tx.blocking_send(res).is_err() && res_ok {
|
||||||
|
// The ROLLBACK was processed but not acknowledged. This means
|
||||||
|
// that the `Transaction` doesn't know it was rolled back and
|
||||||
|
// will try to rollback again on drop. We need to ignore that
|
||||||
|
// rollback.
|
||||||
|
ignore_next_start_rollback = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::CreateCollation { create_collation } => {
|
Command::CreateCollation { create_collation } => {
|
||||||
|
@ -268,15 +311,17 @@ impl ConnectionWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn begin(&mut self) -> Result<(), Error> {
|
pub(crate) async fn begin(&mut self) -> Result<(), Error> {
|
||||||
self.oneshot_cmd(|tx| Command::Begin { tx }).await?
|
self.oneshot_cmd_with_ack(|tx| Command::Begin { tx })
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn commit(&mut self) -> Result<(), Error> {
|
pub(crate) async fn commit(&mut self) -> Result<(), Error> {
|
||||||
self.oneshot_cmd(|tx| Command::Commit { tx }).await?
|
self.oneshot_cmd_with_ack(|tx| Command::Commit { tx })
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn rollback(&mut self) -> Result<(), Error> {
|
pub(crate) async fn rollback(&mut self) -> Result<(), Error> {
|
||||||
self.oneshot_cmd(|tx| Command::Rollback { tx: Some(tx) })
|
self.oneshot_cmd_with_ack(|tx| Command::Rollback { tx: Some(tx) })
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,6 +349,20 @@ impl ConnectionWorker {
|
||||||
rx.await.map_err(|_| Error::WorkerCrashed)
|
rx.await.map_err(|_| Error::WorkerCrashed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn oneshot_cmd_with_ack<F, T>(&mut self, command: F) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
F: FnOnce(rendezvous_oneshot::Sender<T>) -> Command,
|
||||||
|
{
|
||||||
|
let (tx, rx) = rendezvous_oneshot::channel();
|
||||||
|
|
||||||
|
self.command_tx
|
||||||
|
.send_async(command(tx))
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::WorkerCrashed)?;
|
||||||
|
|
||||||
|
rx.recv().await.map_err(|_| Error::WorkerCrashed)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_collation(
|
pub fn create_collation(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -387,3 +446,41 @@ fn prepare(conn: &mut ConnectionState, query: &str) -> Result<SqliteStatement<'s
|
||||||
fn update_cached_statements_size(conn: &ConnectionState, size: &AtomicUsize) {
|
fn update_cached_statements_size(conn: &ConnectionState, size: &AtomicUsize) {
|
||||||
size.store(conn.statements.len(), Ordering::Release);
|
size.store(conn.statements.len(), Ordering::Release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A oneshot channel where send completes only after the receiver receives the value.
|
||||||
|
mod rendezvous_oneshot {
|
||||||
|
use super::oneshot::{self, Canceled};
|
||||||
|
|
||||||
|
pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
|
||||||
|
let (inner_tx, inner_rx) = oneshot::channel();
|
||||||
|
(Sender { inner: inner_tx }, Receiver { inner: inner_rx })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sender<T> {
|
||||||
|
inner: oneshot::Sender<(T, oneshot::Sender<()>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Sender<T> {
|
||||||
|
pub async fn send(self, value: T) -> Result<(), Canceled> {
|
||||||
|
let (ack_tx, ack_rx) = oneshot::channel();
|
||||||
|
self.inner.send((value, ack_tx)).map_err(|_| Canceled)?;
|
||||||
|
ack_rx.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blocking_send(self, value: T) -> Result<(), Canceled> {
|
||||||
|
futures_executor::block_on(self.send(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Receiver<T> {
|
||||||
|
inner: oneshot::Receiver<(T, oneshot::Sender<()>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Receiver<T> {
|
||||||
|
pub async fn recv(self) -> Result<T, Canceled> {
|
||||||
|
let (value, ack_tx) = self.inner.await?;
|
||||||
|
ack_tx.send(()).map_err(|_| Canceled)?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue