use std::{
ffi::CString,
ptr,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
thread::{self, JoinHandle},
};
use either::Either;
use tokio::sync::{Mutex, oneshot};
use crate::{
QueryResult, Row,
error::{Error, Result},
sqlite::{
Arguments,
connection::{
ConnectionState, DbStatus, DbStatusKind, WalCheckpoint, WalCheckpointMode,
establish::EstablishParams, execute,
},
ffi,
statement::Statement,
},
transaction::{
begin_ansi_transaction_sql, commit_ansi_transaction_sql, rollback_ansi_transaction_sql,
},
};
pub struct ConnectionWorker {
command_tx: flume::Sender<Command>,
pub(crate) shared: Arc<WorkerSharedState>,
join_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
}
unsafe impl Sync for ConnectionWorker {}
pub struct WorkerSharedState {
pub(crate) cached_statements_size: AtomicUsize,
pub(crate) conn: Mutex<ConnectionState>,
}
#[allow(dead_code)]
enum Command {
Prepare {
query: Box<str>,
tx: oneshot::Sender<Result<Statement>>,
},
Execute {
query: Box<str>,
arguments: Option<Arguments>,
tx: flume::Sender<Result<Either<QueryResult, Row>>>,
},
Begin {
tx: rendezvous_oneshot::Sender<Result<()>>,
},
Commit {
tx: rendezvous_oneshot::Sender<Result<()>>,
},
Rollback {
tx: Option<rendezvous_oneshot::Sender<Result<()>>>,
},
DbStatus {
kind: DbStatusKind,
reset_highwater: bool,
tx: oneshot::Sender<Result<DbStatus>>,
},
WalCheckpoint {
schema: Option<CString>,
mode: WalCheckpointMode,
tx: oneshot::Sender<Result<WalCheckpoint>>,
},
ParserDepthLimit {
tx: oneshot::Sender<Result<u32>>,
},
#[cfg(test)]
ClearCache {
tx: oneshot::Sender<()>,
},
Shutdown {
tx: oneshot::Sender<Result<()>>,
},
}
impl ConnectionWorker {
pub(crate) async fn establish(params: EstablishParams) -> Result<Self> {
let (establish_tx, establish_rx) = oneshot::channel();
let join_handle = thread::Builder::new()
.name(params.thread_name.clone())
.spawn(move || {
let (command_tx, command_rx) = flume::bounded(params.command_channel_size);
let conn = match params.establish() {
Ok(conn) => conn,
Err(e) => {
establish_tx.send(Err(e)).ok();
return;
}
};
let shared = Arc::new(WorkerSharedState {
cached_statements_size: AtomicUsize::new(0),
conn: Mutex::new(conn),
});
let mut conn = match shared.conn.try_lock() {
Ok(lock) => lock,
Err(e) => {
establish_tx.send(Err(e.into())).ok();
return;
}
};
if establish_tx
.send(Ok((command_tx, Arc::clone(&shared))))
.is_err()
{
return;
}
let mut ignore_next_start_rollback = false;
for cmd in command_rx {
match cmd {
Command::Prepare { query, tx } => {
tx.send(prepare(&mut conn, &query).inspect(|_prepared| {
update_cached_statements_size(
&conn,
&shared.cached_statements_size,
);
}))
.ok();
}
Command::Execute {
query,
arguments,
tx,
} => {
let iter = match execute::iter(&mut conn, &query, arguments)
{
Ok(iter) => iter,
Err(e) => {
tx.send(Err(e)).ok();
continue;
}
};
for res in iter {
if tx.send(res).is_err() {
break;
}
}
update_cached_statements_size(&conn, &shared.cached_statements_size);
}
Command::Begin { tx } => {
let depth = conn.transaction_depth;
let res =
conn.handle
.exec(begin_ansi_transaction_sql(depth))
.map(|_| {
conn.transaction_depth += 1;
});
let res_ok = res.is_ok();
if tx.blocking_send(res).is_err() && res_ok {
if let Err(error) = conn
.handle
.exec(rollback_ansi_transaction_sql(depth + 1))
.map(|_| {
conn.transaction_depth -= 1;
})
{
tracing::error!(%error, "failed to rollback cancelled transaction");
break;
}
}
}
Command::Commit { tx } => {
let depth = conn.transaction_depth;
let res = if depth > 0 {
conn.handle
.exec(commit_ansi_transaction_sql(depth))
.map(|_| {
conn.transaction_depth -= 1;
})
} else {
Ok(())
};
let res_ok = res.is_ok();
if tx.blocking_send(res).is_err() && res_ok {
ignore_next_start_rollback = true;
}
}
Command::Rollback { tx } => {
if ignore_next_start_rollback && tx.is_none() {
ignore_next_start_rollback = false;
continue;
}
let depth = conn.transaction_depth;
let res = if depth > 0 {
let sql = if depth == 1 {
rollback_ansi_transaction_sql(depth)
} else {
format!(
"{}; {}",
rollback_ansi_transaction_sql(depth),
commit_ansi_transaction_sql(depth)
)
};
conn.handle.exec(sql).map(|_| {
conn.transaction_depth -= 1;
})
} else {
Ok(())
};
let res_ok = res.is_ok();
if let Some(tx) = tx && tx.blocking_send(res).is_err() && res_ok {
ignore_next_start_rollback = true;
}
}
Command::DbStatus {
kind,
reset_highwater,
tx,
} => {
tx.send(db_status(&conn, kind, reset_highwater)).ok();
}
Command::WalCheckpoint { schema, mode, tx } => {
tx.send(wal_checkpoint(&conn, schema.as_ref(), mode)).ok();
}
Command::ParserDepthLimit { tx } => {
tx.send(parser_depth_limit(&conn)).ok();
}
#[cfg(test)]
Command::ClearCache { tx } => {
conn.statements.clear();
update_cached_statements_size(&conn, &shared.cached_statements_size);
tx.send(()).ok();
}
Command::Shutdown { tx } => {
conn.statements.clear();
let res = conn.handle.close();
drop(conn);
drop(shared);
let _send_result = tx.send(res);
return;
}
}
}
})?;
let (command_tx, shared) = establish_rx.await.map_err(|_| Error::WorkerCrashed)??;
Ok(Self {
command_tx,
shared,
join_handle: Arc::new(Mutex::new(Some(join_handle))),
})
}
#[allow(dead_code)]
pub(crate) fn is_shutdown(&self) -> bool {
false
}
#[allow(dead_code)]
pub(crate) async fn prepare(&self, query: &str) -> Result<Statement> {
self.oneshot_cmd(|tx| Command::Prepare {
query: query.into(),
tx,
})
.await?
}
pub(crate) async fn execute(
&self,
query: String,
args: Option<Arguments>,
chan_size: usize,
) -> Result<flume::Receiver<Result<Either<QueryResult, Row>>>> {
let (tx, rx) = flume::bounded(chan_size);
self.command_tx
.send_async(Command::Execute {
query: query.into(),
arguments: args,
tx,
})
.await
.map_err(|_| Error::WorkerCrashed)?;
Ok(rx)
}
pub(crate) async fn begin(&self) -> Result<()> {
self.oneshot_cmd_with_ack(|tx| Command::Begin { tx })
.await?
}
pub(crate) async fn commit(&self) -> Result<()> {
self.oneshot_cmd_with_ack(|tx| Command::Commit { tx })
.await?
}
pub(crate) async fn rollback(&self) -> Result<()> {
self.oneshot_cmd_with_ack(|tx| Command::Rollback { tx: Some(tx) })
.await?
}
pub(crate) fn start_rollback(&self) -> Result<()> {
self.command_tx
.send(Command::Rollback { tx: None })
.map_err(|_| Error::WorkerCrashed)
}
pub(crate) async fn db_status(
&self,
kind: DbStatusKind,
reset_highwater: bool,
) -> Result<DbStatus> {
self.oneshot_cmd(|tx| Command::DbStatus {
kind,
reset_highwater,
tx,
})
.await?
}
pub(crate) async fn wal_checkpoint(
&self,
schema: Option<CString>,
mode: WalCheckpointMode,
) -> Result<WalCheckpoint> {
self.oneshot_cmd(|tx| Command::WalCheckpoint { schema, mode, tx })
.await?
}
pub(crate) async fn parser_depth_limit(&self) -> Result<u32> {
self.oneshot_cmd(|tx| Command::ParserDepthLimit { tx })
.await?
}
#[allow(dead_code)]
async fn oneshot_cmd<F, T>(&self, command: F) -> Result<T>
where
F: FnOnce(oneshot::Sender<T>) -> Command,
{
let (tx, rx) = oneshot::channel();
self.command_tx
.send_async(command(tx))
.await
.map_err(|_| Error::WorkerCrashed)?;
rx.await.map_err(|_| Error::WorkerCrashed)
}
async fn oneshot_cmd_with_ack<F, T>(&self, command: F) -> Result<T>
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)
}
#[cfg(test)]
pub(crate) async fn clear_cache(&self) -> Result<()> {
self.oneshot_cmd(|tx| Command::ClearCache { tx }).await
}
pub(crate) async fn shutdown(&self) -> Result<()> {
let join_handle = self.join_handle.lock().await.take();
let (tx, rx) = oneshot::channel();
let send_res = self
.command_tx
.send(Command::Shutdown { tx })
.map_err(|_| Error::WorkerCrashed);
if let Err(e) = send_res {
if let Some(handle) = join_handle {
let _join_result = handle.join();
}
return Err(e);
}
let res = rx.await.map_err(|_| Error::WorkerCrashed)?;
res?;
if let Some(handle) = join_handle {
handle.join().map_err(|_| Error::WorkerCrashed)?;
}
Ok(())
}
}
fn prepare(conn: &mut ConnectionState, query: &str) -> Result<Statement> {
let statement = conn.statements.get(query)?;
while let Some(_statement) = statement.prepare_next(&conn.handle)? {
}
Ok(Statement {
sql: query.to_string(),
})
}
fn update_cached_statements_size(conn: &ConnectionState, size: &AtomicUsize) {
size.store(conn.statements.len(), Ordering::Release);
}
fn db_status(
conn: &ConnectionState,
kind: DbStatusKind,
reset_highwater: bool,
) -> Result<DbStatus> {
let (current, highwater) =
ffi::db_status64(conn.handle.as_ptr(), kind.as_sqlite_code(), reset_highwater)?;
Ok(DbStatus { current, highwater })
}
fn wal_checkpoint(
conn: &ConnectionState,
schema: Option<&CString>,
mode: WalCheckpointMode,
) -> Result<WalCheckpoint> {
let schema_ptr = schema.map_or(ptr::null(), |schema| schema.as_ptr());
let (log_frames, checkpointed_frames) =
ffi::wal_checkpoint_v2(conn.handle.as_ptr(), schema_ptr, mode.as_sqlite_code())?;
Ok(WalCheckpoint {
log_frames: frames_to_option(log_frames),
checkpointed_frames: frames_to_option(checkpointed_frames),
})
}
fn parser_depth_limit(conn: &ConnectionState) -> Result<u32> {
let limit = ffi::limit(
conn.handle.as_ptr(),
libsqlite3_sys::SQLITE_LIMIT_PARSER_DEPTH,
-1,
);
u32::try_from(limit).map_err(|_| {
Error::Protocol(format!(
"SQLite returned invalid parser depth limit {limit}"
))
})
}
fn frames_to_option(frames: i32) -> Option<i32> {
if frames < 0 { None } else { Some(frames) }
}
mod rendezvous_oneshot {
use std::result::Result as StdResult;
use super::oneshot;
#[derive(Debug)]
pub struct 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) -> StdResult<(), Canceled> {
let (ack_tx, ack_rx) = oneshot::channel();
self.inner.send((value, ack_tx)).map_err(|_| Canceled)?;
ack_rx.await.map_err(|_| Canceled)?;
Ok(())
}
pub fn blocking_send(self, value: T) -> StdResult<(), 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) -> StdResult<T, Canceled> {
let (value, ack_tx) = self.inner.await.map_err(|_| Canceled)?;
ack_tx.send(()).map_err(|_| Canceled)?;
Ok(value)
}
}
}