use std::collections::{HashMap, HashSet, VecDeque};
use std::panic::AssertUnwindSafe;
use std::sync::{mpsc, Arc, Mutex, Weak};
use std::time::{Duration, Instant};
use rusqlite::Connection;
use serde_json::Value;
use tokio::sync::{oneshot, OwnedSemaphorePermit};
use super::keys::{require_key, KeyRangeArg, Range};
use super::query::{self, Cursor, Query, ReadResult, WriteOp};
use super::{Caller, Core, DbFile};
use crate::error::{Error, Result};
pub(crate) type Job = Box<dyn FnOnce(&mut Connection) + Send + 'static>;
pub(crate) fn closed() -> Error {
Error::invalid_state("db_closed")
}
pub(crate) async fn reply<T>(rx: oneshot::Receiver<T>) -> Result<T> {
rx.await.map_err(|_| closed())
}
pub(crate) struct Worker {
sender: Mutex<Option<mpsc::Sender<Job>>>,
done: Mutex<Option<mpsc::Receiver<()>>>,
}
impl Worker {
pub fn spawn(name: String, conn: Connection) -> Result<Worker> {
let (sender, jobs) = mpsc::channel::<Job>();
let (done_tx, done) = mpsc::channel();
std::thread::Builder::new()
.name(name)
.spawn(move || {
let mut conn = conn;
while let Ok(job) = jobs.recv() {
if std::panic::catch_unwind(AssertUnwindSafe(|| job(&mut conn))).is_err() {
log::error!("数据库线程中的操作 panic,已跳过");
}
}
drop(conn);
let _ = done_tx.send(());
})
.map_err(|e| Error::internal(format!("创建数据库线程失败:{e}")))?;
Ok(Worker { sender: Mutex::new(Some(sender)), done: Mutex::new(Some(done)) })
}
pub fn send(&self, job: Job) -> Result<()> {
match self.sender.lock().unwrap().as_ref() {
Some(sender) => sender.send(job).map_err(|_| closed()),
None => Err(closed()),
}
}
pub fn call<T: Send + 'static>(&self, f: impl FnOnce(&mut Connection) -> T + Send + 'static) -> Result<oneshot::Receiver<T>> {
let (tx, rx) = oneshot::channel();
self.send(Box::new(move |conn| {
let _ = tx.send(f(conn));
}))?;
Ok(rx)
}
pub fn close(&self) {
self.sender.lock().unwrap().take();
}
pub fn wait(&self, timeout: Duration) -> bool {
let Some(done) = self.done.lock().unwrap().take() else { return true };
match done.recv_timeout(timeout) {
Ok(()) | Err(mpsc::RecvTimeoutError::Disconnected) => true,
Err(mpsc::RecvTimeoutError::Timeout) => {
*self.done.lock().unwrap() = Some(done);
false
}
}
}
}
pub(crate) enum Lease {
Write(#[allow(dead_code)] OwnedSemaphorePermit),
Read { file: Arc<DbFile>, slot: usize, _permit: OwnedSemaphorePermit },
}
impl Drop for Lease {
fn drop(&mut self) {
if let Lease::Read { file, slot, .. } = self {
file.return_reader(*slot);
}
}
}
struct TxState {
ended: Option<&'static str>,
active: usize,
last_end: Instant,
}
pub(crate) struct Tx {
pub id: u64,
pub db: u64,
pub file: Arc<DbFile>,
pub label: String,
pub instance: u64,
pub write: bool,
pub stores: Vec<String>,
slot: Option<usize>,
lease: Mutex<Option<Lease>>,
state: Mutex<TxState>,
}
fn ended_error(reason: &str) -> Error {
match reason {
"timeout" => Error::new("invalid_state", Some("tx_timeout"), "事务空闲超时,已回滚"),
_ => Error::new("invalid_state", Some("tx_finished"), format!("事务已结束({reason})")),
}
}
impl Tx {
fn worker(&self) -> &Worker {
match self.slot {
None => &self.file.writer,
Some(i) => &self.file.readers[i],
}
}
pub async fn run<T: Send + 'static>(&self, f: impl FnOnce(&mut Connection) -> Result<T> + Send + 'static) -> Result<T> {
let rx = {
let mut state = self.state.lock().unwrap();
if let Some(reason) = state.ended {
return Err(ended_error(reason));
}
let rx = self.worker().call(move |conn| {
if conn.is_autocommit() {
return Err(Error::invalid_state("tx_rolled_back"));
}
f(conn)
})?;
state.active += 1;
rx
};
let _guard = OpGuard(self);
reply(rx).await?
}
fn end(&self, reason: &'static str) -> bool {
let mut state = self.state.lock().unwrap();
if state.ended.is_some() {
return false;
}
state.ended = Some(reason);
true
}
fn try_expire(&self, idle: Duration) -> bool {
let mut state = self.state.lock().unwrap();
if state.ended.is_none() && state.active == 0 && state.last_end.elapsed() >= idle {
state.ended = Some("timeout");
return true;
}
false
}
pub fn rollback(&self) {
let _ = self.worker().send(Box::new(|conn| {
let _ = end_sql(conn, false);
}));
self.lease.lock().unwrap().take();
}
fn release(&self) {
self.lease.lock().unwrap().take();
}
}
struct OpGuard<'a>(&'a Tx);
impl Drop for OpGuard<'_> {
fn drop(&mut self) {
let mut state = self.0.state.lock().unwrap();
state.active -= 1;
state.last_end = Instant::now();
}
}
fn begin_sql(conn: &mut Connection, write: bool, strict: bool) -> Result<()> {
if !conn.is_autocommit() {
log::warn!("连接上有未结束的事务,先回滚");
conn.execute_batch("ROLLBACK")?;
}
if write {
conn.execute_batch(if strict { "PRAGMA synchronous = FULL" } else { "PRAGMA synchronous = NORMAL" })?;
conn.execute_batch("BEGIN IMMEDIATE")?;
} else {
conn.execute_batch("BEGIN")?;
if let Err(err) = conn.query_row("SELECT count(*) FROM _dr_schema", [], |_| Ok(())) {
let _ = conn.execute_batch("ROLLBACK");
return Err(err.into());
}
}
Ok(())
}
fn end_sql(conn: &mut Connection, commit: bool) -> Result<()> {
let result = if commit && !conn.is_autocommit() { conn.execute_batch("COMMIT").map_err(Error::from) } else { Ok(()) };
if !conn.is_autocommit() {
let _ = conn.execute_batch("ROLLBACK");
}
result
}
#[derive(Default)]
struct TableInner {
live: HashMap<u64, Arc<Tx>>,
ended: VecDeque<(u64, &'static str)>,
released: HashSet<u64>,
}
const ENDED_KEEP: usize = 256;
#[derive(Default)]
pub(crate) struct TxTable {
inner: Mutex<TableInner>,
}
impl TxTable {
fn insert(&self, tx: Arc<Tx>) -> bool {
let mut inner = self.inner.lock().unwrap();
if inner.released.contains(&tx.instance) || tx.file.is_closed() {
return false;
}
inner.live.insert(tx.id, tx);
true
}
fn get(&self, id: u64, caller: &Caller) -> Result<Arc<Tx>> {
let inner = self.inner.lock().unwrap();
match inner.live.get(&id) {
Some(tx) if tx.instance == caller.instance && tx.label == caller.label => Ok(tx.clone()),
Some(_) => Err(Error::invalid_state("tx_not_found")),
None => match inner.ended.iter().find(|(i, _)| *i == id) {
Some((_, reason)) => Err(ended_error(reason)),
None => Err(Error::invalid_state("tx_not_found")),
},
}
}
fn remove(&self, id: u64, reason: &'static str) {
let mut inner = self.inner.lock().unwrap();
inner.live.remove(&id);
push_ended(&mut inner, id, reason);
}
fn take_where(&self, reason: &'static str, pred: impl Fn(&Tx) -> bool) -> Vec<Arc<Tx>> {
let mut inner = self.inner.lock().unwrap();
let ids: Vec<u64> = inner.live.values().filter(|tx| pred(tx)).map(|tx| tx.id).collect();
let mut out = Vec::with_capacity(ids.len());
for id in ids {
if let Some(tx) = inner.live.remove(&id) {
out.push(tx);
}
push_ended(&mut inner, id, reason);
}
out
}
pub fn mark_released(&self, instance: u64) {
self.inner.lock().unwrap().released.insert(instance);
}
pub fn is_released(&self, instance: u64) -> bool {
self.inner.lock().unwrap().released.contains(&instance)
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.inner.lock().unwrap().live.len()
}
}
fn push_ended(inner: &mut TableInner, id: u64, reason: &'static str) {
if inner.ended.len() >= ENDED_KEEP {
inner.ended.pop_front();
}
inner.ended.push_back((id, reason));
}
pub(crate) fn start_reaper(core: Weak<Core>, idle: Duration) {
let tick = (idle / 5).clamp(Duration::from_millis(10), Duration::from_secs(1));
let spawned = std::thread::Builder::new().name("drim-db-idle".into()).spawn(move || loop {
std::thread::sleep(tick);
let Some(core) = core.upgrade() else { return };
core.expire_idle();
});
if let Err(err) = spawned {
log::error!("创建事务空闲检查线程失败:{err}");
}
}
pub struct ReadRequest {
pub op: String,
pub store: String,
pub key: Option<Value>,
pub index: Option<String>,
pub range: Option<KeyRangeArg>,
pub limit: Option<u64>,
pub direction: Option<String>,
pub cursor: Option<String>,
}
impl Core {
pub(crate) async fn begin(&self, caller: &Caller, db: u64, stores: Vec<String>, mode: &str, durability: Option<&str>) -> Result<u64> {
let write = match mode {
"readwrite" => true,
"readonly" => false,
_ => return Err(Error::validation("invalid_mode", format!("不认识的事务模式 {mode}"))),
};
if write && !caller.engine {
return Err(Error::not_engine());
}
let file = self.handle_file(db, caller)?;
if stores.is_empty() {
return Err(Error::validation("unknown_store", "事务没有对象仓库"));
}
{
let schema = file.schema();
if let Some(name) = stores.iter().find(|s| schema.store(s).is_none()) {
return Err(Error::validation("unknown_store", format!("没有对象仓库 {name}")));
}
}
let strict = write && durability == Some("strict");
let (lease, slot) = if write {
let permit = file.write_gate.clone().acquire_owned().await.map_err(|_| closed())?;
(Lease::Write(permit), None)
} else {
let permit = file.read_gate.clone().acquire_owned().await.map_err(|_| closed())?;
let slot = file.take_reader().ok_or_else(closed)?;
(Lease::Read { file: file.clone(), slot, _permit: permit }, Some(slot))
};
let tx = Arc::new(Tx {
id: self.next_id(),
db,
file,
label: caller.label.clone(),
instance: caller.instance,
write,
stores,
slot,
lease: Mutex::new(Some(lease)),
state: Mutex::new(TxState { ended: None, active: 0, last_end: Instant::now() }),
});
let rx = tx.worker().call(move |conn| begin_sql(conn, write, strict))?;
reply(rx).await??;
tx.state.lock().unwrap().last_end = Instant::now();
if !self.txs.insert(tx.clone()) {
tx.end("released");
tx.rollback();
return Err(Error::aborted());
}
Ok(tx.id)
}
pub(crate) async fn read(&self, caller: &Caller, id: u64, req: ReadRequest) -> Result<ReadResult> {
let tx = self.txs.get(id, caller)?;
let schema = tx.file.schema();
let def = query::store_in_scope(&schema, &tx.stores, &req.store)?;
let column = match &req.index {
Some(name) => Some(def.index(name).ok_or_else(|| Error::validation("unknown_index", format!("{} 上没有索引 {name}", req.store)))?.column.clone()),
None => None,
};
let range = match (&req.range, &req.key) {
(Some(range), _) => range.encode()?,
(None, Some(key)) if req.op != "get" => {
let key = require_key(key)?;
Range { lower: Some((key.clone(), false)), upper: Some((key, false)) }
}
_ => Range::default(),
};
let reverse = match req.direction.as_deref() {
None | Some("next") => false,
Some("prev") => true,
Some(other) => return Err(Error::validation("invalid_direction", format!("不认识的方向 {other}"))),
};
let store = req.store.clone();
match req.op.as_str() {
"get" => {
let key = require_key(req.key.as_ref().unwrap_or(&Value::Null))?;
tx.run(move |conn| query::get(conn, &store, &key).map(ReadResult::One)).await
}
"get_all" | "count" | "iterate" => {
let after = match (&req.cursor, req.op.as_str()) {
(Some(text), "iterate") => Some(Cursor::parse(text, column.is_some())?),
_ => None,
};
let q = Query { store, column, range, reverse, limit: req.limit };
match req.op.as_str() {
"get_all" => tx.run(move |conn| query::get_all(conn, &q).map(ReadResult::Many)).await,
"count" => tx.run(move |conn| query::count(conn, &q).map(ReadResult::Count)).await,
_ => tx.run(move |conn| query::iterate(conn, &q, after.as_ref())).await,
}
}
other => Err(Error::validation("invalid_op", format!("不认识的读取 {other}"))),
}
}
pub(crate) async fn write(&self, caller: &Caller, id: u64, ops: Vec<WriteOp>) -> Result<()> {
let tx = self.txs.get(id, caller)?;
if !tx.write {
return Err(Error::invalid_state("readonly_transaction"));
}
let prepared = query::prepare(&tx.file.schema(), &tx.stores, ops)?;
let result = tx.run(move |conn| query::apply_batch(conn, &prepared)).await;
self.check_full(&tx, &result);
result
}
pub(crate) async fn commit(&self, caller: &Caller, id: u64, ops: Vec<WriteOp>) -> Result<()> {
let tx = self.txs.get(id, caller)?;
if !tx.write && !ops.is_empty() {
return Err(Error::invalid_state("readonly_transaction"));
}
let prepared = query::prepare(&tx.file.schema(), &tx.stores, ops);
{
let mut state = tx.state.lock().unwrap();
if let Some(reason) = state.ended {
return Err(ended_error(reason));
}
state.ended = Some("committed");
}
self.txs.remove(tx.id, "committed");
let prepared = match prepared {
Ok(p) => p,
Err(err) => {
tx.rollback();
return Err(err);
}
};
let rx = tx.worker().call(move |conn| match query::apply(conn, &prepared) {
Ok(()) => end_sql(conn, true),
Err(err) => {
let _ = end_sql(conn, false);
Err(err)
}
});
tx.release();
let result = match rx {
Ok(rx) => reply(rx).await.and_then(|r| r),
Err(err) => Err(err),
};
self.check_full(&tx, &result);
result
}
pub(crate) fn abort(&self, caller: &Caller, id: u64) {
let Ok(tx) = self.txs.get(id, caller) else { return };
if tx.end("aborted") {
self.txs.remove(tx.id, "aborted");
tx.rollback();
}
}
pub(crate) fn expire_idle(&self) {
let idle = self.idle;
for tx in self.txs.take_where("timeout", |tx| tx.try_expire(idle)) {
if tx.write {
log::error!("写事务 {} 两次操作之间空闲超过 {} 毫秒,已回滚(引擎页可能卡住)", tx.id, idle.as_millis());
} else {
log::warn!("只读事务 {} 空闲超过 {} 毫秒,已结束并放开连接", tx.id, idle.as_millis());
}
tx.rollback();
}
}
pub(crate) fn rollback_where(&self, reason: &'static str, pred: impl Fn(&Tx) -> bool) {
for tx in self.txs.take_where(reason, |tx| pred(tx) && tx.end(reason)) {
log::debug!("回滚事务 {}({reason})", tx.id);
tx.rollback();
}
}
fn check_full(&self, tx: &Tx, result: &Result<()>) {
if let Err(err) = result {
if err.code == "storage_error" && err.reason.as_deref() == Some("quota_exceeded") {
self.storage_full(&tx.file.app_key, &tx.file.username);
}
}
}
}
#[cfg(test)]
impl Core {
pub(crate) async fn run_in_tx(&self, caller: &Caller, id: u64, f: impl FnOnce(&mut Connection) -> Result<()> + Send + 'static) -> Result<()> {
self.txs.get(id, caller)?.run(f).await
}
}