use std::mem;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::collections::{VecDeque, hash_map::Entry as HashMapEntry};
use std::sync::{Arc,
atomic::{AtomicBool, AtomicUsize, Ordering}};
use parking_lot::Mutex;
use futures::{future::{FutureExt, BoxFuture},
stream::{StreamExt, BoxStream}};
use async_lock::Mutex as AsyncMutex;
use async_channel::Sender;
use async_stream::stream;
use log::{debug, info, error};
use pi_async_rt::{lock::spin_lock::SpinLock,
rt::{AsyncRuntime,
multi_thread::MultiTaskRuntime}};
use pi_atom::Atom;
use pi_guid::Guid;
use pi_hash::XHashMap;
use pi_ordmap::{ordmap::OrdMap, asbtree::Tree};
use pi_async_transaction::{AsyncTransaction,
Transaction2Pc,
Transaction2PcAllConflicts,
UnitTransaction,
SequenceTransaction,
TransactionTree,
TransactionError,
AsyncCommitLog,
ErrorLevel,
manager_2pc::Transaction2PcStatus};
use pi_ordmap::ordmap::ImOrdMap;
use pi_store::log_store::log_file::{PairLoader,
LogMethod,
LogFile};
use crate::{Binary, KVAction, TableTrQos, KVActionLog, KVDBCommitConfirm, KVTableTrError,
TableKey,
db::{KVDBTransaction, KVDBChildTrList},
key_version::{KeyVersions,
PrepareMode,
PreparedActions,
TableVersionContext,
Version,
VersionReceipt,
binary_state_equal,
has_prepared_conflict},
tables::{KVTable, ordmap_snapshot::OrdMapSnapshot},
utils::KVDBEvent,
KVDBTableType};
const DEFAULT_LOG_FILE_COMMIT_DELAY_TIMEOUT: usize = 1000;
#[derive(Clone)]
pub struct MetaTable<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
>(Arc<InnerMetaTable<C, Log>>);
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Send for MetaTable<C, Log> {}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Sync for MetaTable<C, Log> {}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> KVTable for MetaTable<C, Log> {
type Name = Atom;
type Tr = MetaTabTr<C, Log>;
type Error = KVTableTrError;
fn name(&self) -> <Self as KVTable>::Name {
self.0.name.clone()
}
fn path(&self) -> Option<&Path> {
Some(self.0.log_file.path())
}
#[inline]
fn is_persistent(&self) -> bool {
true
}
fn is_ordered(&self) -> bool {
true
}
fn len(&self) -> usize {
self.0.root.lock().size()
}
fn size(&self) -> u64 {
let root_copy = self.0.root.lock().clone();
root_copy.full_bytes_size()
}
fn transaction(&self,
source: Atom,
is_writable: bool,
is_persistent: bool,
prepare_timeout: u64,
commit_timeout: u64) -> Self::Tr {
MetaTabTr::new(source,
is_writable,
is_persistent,
prepare_timeout,
commit_timeout,
self.clone())
}
fn ready_collect(&self) -> BoxFuture<Result<(), Self::Error>> {
let table = self.clone();
async move {
let now = Instant::now();
match table.0.log_file.split().await {
Err(e) => {
return Err(KVTableTrError::new_transaction_error(ErrorLevel::Normal,
format!("Ready collect meta table failed, path: {:?}, table: {:?}, reason: {:?}",
table.0.log_file.path(),
table.0.name.as_str(),
e)));
},
Ok(writed_log_index) => {
info!("Ready collect meta table succeeded, time: {:?}, path: {:?}, table: {:?}, writed_log_index: {}",
now.elapsed(),
table.0.log_file.path(),
table.0.name.as_str(),
writed_log_index);
Ok(())
},
}
}.boxed()
}
fn collect(&self) -> BoxFuture<Result<(), Self::Error>> {
let table = self.clone();
async move {
let now = Instant::now();
match table.0.log_file.collect(1024 * 1024,
32 * 1024,
false).await {
Err(e) => {
return Err(KVTableTrError::new_transaction_error(ErrorLevel::Normal,
format!("Collect meta table failed, path: {:?}, table: {:?}, reason: {:?}",
table.0.log_file.path(),
table.0.name.as_str(),
e)));
},
Ok((size, len)) => {
info!("Collect meta table succeeded, time: {:?}, path: {:?}, table: {:?}, file_size: {}, file_len: {}",
now.elapsed(),
table.0.log_file.path(),
table.0.name.as_str(),
size,
len);
Ok(())
},
}
}.boxed()
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> MetaTable<C, Log> {
pub(crate) fn query_committed(&self, key: &Binary) -> Option<Binary> {
self.0.root.lock().get(key).cloned()
}
pub async fn new<P: AsRef<Path>>(rt: MultiTaskRuntime<()>,
path: P,
name: Atom,
log_file_limit: usize,
block_limit: usize,
init_log_file_index: Option<usize>,
load_buf_len: u64,
is_checksum: bool,
waits_limit: usize,
wait_timeout: usize,
notifier: Option<Sender<KVDBEvent<Guid>>>) -> Self {
let root = Mutex::new(OrdMap::new(None));
let prepare = Mutex::new(XHashMap::default());
match LogFile::open(rt.clone(),
path.as_ref().to_path_buf(),
block_limit,
log_file_limit,
init_log_file_index).await {
Err(e) => {
panic!("Open meta table failed, table: {:?}, path: {:?}, reason: {:?}",
name.as_str(),
path.as_ref(),
e);
},
Ok(log_file) => {
let waits = AsyncMutex::new(VecDeque::new());
let waits_size = AtomicUsize::new(0);
let collecting = AtomicBool::new(false);
let inner = InnerMetaTable {
name: name.clone(),
root,
prepare,
rt,
waits,
waits_size,
waits_limit,
wait_timeout,
collecting,
log_file,
notifier,
};
let table = MetaTable(Arc::new(inner));
let now = Instant::now();
let mut loader = MetaTableLoader::new(table.clone());
if let Err(e) = table.0.log_file.load(&mut loader,
None,
load_buf_len,
is_checksum).await {
panic!("Load meta table failed, table: {:?}, path: {:?}, reason: {:?}",
name.as_str(),
path.as_ref(),
e);
}
info!("Load meta table succeeded, table: {:?}, path: {:?}, files: {}, keys: {}, bytes: {}, time: {:?}",
name.as_str(),
path.as_ref(),
loader.log_files_len(),
loader.keys_len(),
loader.bytes_len(),
now.elapsed());
let table_copy = table.clone();
let _ = table.0.rt.spawn(async move {
let table_ref = &table_copy;
loop {
match collect_waits(table_ref,
Some(table_copy.0.wait_timeout)).await {
Err((collect_time, statistics)) => {
error!("Collect meta table failed, table: {:?}, time: {:?}, statistics: {:?}, reason: out of time",
table_copy.name().as_str(),
collect_time,
statistics);
},
Ok((collect_time, statistics)) => {
debug!("Collect meta table succeeded, table: {:?}, time: {:?}, statistics: {:?}, reason: out of time",
table_copy.name().as_str(),
collect_time,
statistics);
},
}
}
});
table
},
}
}
}
struct InnerMetaTable<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> {
name: Atom,
root: Mutex<OrdMap<Tree<Binary, Binary>>>,
prepare: Mutex<XHashMap<Guid, PreparedActions>>,
rt: MultiTaskRuntime<()>,
waits: AsyncMutex<VecDeque<(MetaTabTr<C, Log>, XHashMap<Binary, KVActionLog>, <MetaTabTr<C, Log> as Transaction2Pc>::CommitConfirm)>>,
waits_size: AtomicUsize,
waits_limit: usize,
wait_timeout: usize,
collecting: AtomicBool,
log_file: LogFile,
notifier: Option<Sender<KVDBEvent<Guid>>>,
}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Send for InnerMetaTable<C, Log> {}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Sync for InnerMetaTable<C, Log> {}
#[derive(Clone)]
pub struct MetaTabTr<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
>(Arc<InnerMetaTabTr<C, Log>>);
#[derive(Clone, Copy)]
enum PrepareConflictKind {
Common,
First,
All,
}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Send for MetaTabTr<C, Log> {}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Sync for MetaTabTr<C, Log> {}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> AsyncTransaction for MetaTabTr<C, Log> {
type Output = ();
type Error = KVTableTrError;
fn is_writable(&self) -> bool {
self.0.writable
}
fn is_concurrent_commit(&self) -> bool {
false
}
fn is_concurrent_rollback(&self) -> bool {
false
}
fn get_source(&self) -> Atom {
self.0.source.clone()
}
fn init(&self)
-> BoxFuture<Result<<Self as AsyncTransaction>::Output, <Self as AsyncTransaction>::Error>> {
async move {
Ok(())
}.boxed()
}
fn rollback(&self)
-> BoxFuture<Result<<Self as AsyncTransaction>::Output, <Self as AsyncTransaction>::Error>> {
let tr = self.clone();
async move {
let transaction_uid = tr.get_transaction_uid().unwrap();
let _ = tr.0.table.0.prepare.lock().remove(&transaction_uid);
if let Some(context) = tr.0.version_context.as_ref() {
context.release_snapshot();
}
Ok(())
}.boxed()
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Transaction2Pc for MetaTabTr<C, Log> {
type Tid = Guid;
type Pid = Guid;
type Cid = Guid;
type PrepareOutput = Vec<u8>;
type PrepareError = KVTableTrError;
type ConfirmOutput = ();
type ConfirmError = KVTableTrError;
type CommitConfirm = KVDBCommitConfirm<C, Log>;
fn is_require_persistence(&self) -> bool {
self.0.persistence.load(Ordering::Relaxed)
}
fn require_persistence(&self) {
self.0.persistence.store(true, Ordering::Relaxed);
}
fn is_concurrent_prepare(&self) -> bool {
false
}
fn is_enable_inherit_uid(&self) -> bool {
true
}
fn get_transaction_uid(&self) -> Option<<Self as Transaction2Pc>::Tid> {
self.0.tid.lock().clone()
}
fn set_transaction_uid(&self, uid: <Self as Transaction2Pc>::Tid) {
*self.0.tid.lock() = Some(uid);
}
fn get_prepare_uid(&self) -> Option<<Self as Transaction2Pc>::Pid> {
None
}
fn set_prepare_uid(&self, _uid: <Self as Transaction2Pc>::Pid) {
}
fn get_commit_uid(&self) -> Option<<Self as Transaction2Pc>::Cid> {
self.0.cid.lock().clone()
}
fn set_commit_uid(&self, uid: <Self as Transaction2Pc>::Cid) {
*self.0.cid.lock() = Some(uid);
}
fn get_prepare_timeout(&self) -> u64 {
self.0.prepare_timeout
}
fn get_commit_timeout(&self) -> u64 {
self.0.commit_timeout
}
fn prepare(&self)
-> BoxFuture<Result<Option<<Self as Transaction2Pc>::PrepareOutput>, <Self as Transaction2Pc>::PrepareError>> {
let tr = self.clone();
async move {
tr.prepare_registered(PrepareConflictKind::Common).await
}.boxed()
}
fn prepare_conflicts(&self) -> BoxFuture<Result<Option<<Self as Transaction2Pc>::PrepareOutput>, <Self as Transaction2Pc>::PrepareError>> {
let tr = self.clone();
async move {
tr.prepare_registered(PrepareConflictKind::First).await
}.boxed()
}
fn commit(&self, confirm: <Self as Transaction2Pc>::CommitConfirm)
-> BoxFuture<Result<<Self as AsyncTransaction>::Output, <Self as AsyncTransaction>::Error>> {
let tr = self.clone();
async move {
let transaction_uid = tr.get_transaction_uid().unwrap();
let publication = match tr.0.version_context.as_ref() {
Some(context) => Some(context.versions().publication().write().await),
None => None,
};
let actions = tr
.0
.table
.0
.prepare
.lock()
.remove(&transaction_uid)
.map(|prepared| prepared.actions)
.unwrap_or_default();
let has_writes = actions.values().any(|action| {
matches!(action, KVActionLog::Write(_) | KVActionLog::DirtyWrite(_))
});
if has_writes {
let revision = match tr.0.version_context.as_ref() {
Some(context) => {
match context.versions().checked_next_revision() {
Some(revision) => Some(revision),
None => {
drop(publication);
context.release_snapshot();
return Err(KVTableTrError::new_transaction_error(
ErrorLevel::Fatal,
format!("Commit meta table failed, table: {:?}, source: {:?}, transaction_uid: {:?}, reason: key version revision exhausted",
tr.0.table.name().as_str(),
tr.0.source,
transaction_uid)));
},
}
},
None => None,
};
let mut committed_versions = Vec::new();
let mut root = tr.0.table.0.root.lock();
if root.ptr_eq(&tr.0.root_ref) {
*root = tr.0.root_mut.lock().clone();
} else {
for (key, action) in &actions {
match action {
KVActionLog::Write(None) | KVActionLog::DirtyWrite(None) => {
let _ = root.delete(key, false);
},
KVActionLog::Write(Some(value)) | KVActionLog::DirtyWrite(Some(value)) => {
let _ = root.upsert(key.clone(), value.clone(), false);
},
KVActionLog::Read => (),
}
}
}
if let (Some(context), Some(revision)) =
(tr.0.version_context.as_ref(), revision) {
for (key, action) in &actions {
let value = match action {
KVActionLog::Write(value) | KVActionLog::DirtyWrite(value) => value,
KVActionLog::Read => continue,
};
committed_versions.push(context.versions().publish(
tr.0.table.name(),
key.clone(),
value.as_ref(),
transaction_uid.clone(),
revision));
}
context.versions().complete_revision(revision);
if let Some(receipt) = context.receipt() {
receipt.append(committed_versions);
}
}
}
drop(publication);
if let Some(context) = tr.0.version_context.as_ref() {
context.release_snapshot();
}
if tr.is_require_persistence() {
let table_copy = tr.0.table.clone();
let _ = self.0.table.0.rt.spawn(async move {
let mut size = 0;
for (key, action) in &actions {
match action {
KVActionLog::Write(Some(value)) | KVActionLog::DirtyWrite(Some(value)) => {
size += key.len() + value.len();
},
KVActionLog::Write(None) | KVActionLog::DirtyWrite(None) => {
size += key.len();
},
KVActionLog::Read => (),
}
}
table_copy.0.waits.lock().await.push_back((tr, actions, confirm));
let last_waits_size = table_copy.0.waits_size.fetch_add(size, Ordering::SeqCst); if last_waits_size + size >= table_copy.0.waits_limit {
table_copy.0.waits_size.store(0, Ordering::Relaxed);
match collect_waits(&table_copy,
None).await {
Err((collect_time, statistics)) => {
error!("Collect meta table failed, table: {:?}, time: {:?}, statistics: {:?}, reason: out of size",
table_copy.name().as_str(),
collect_time,
statistics);
},
Ok((collect_time, statistics)) => {
info!("Collect meta table succeeded, table: {:?}, time: {:?}, statistics: {:?}, reason: out of size",
table_copy.name().as_str(),
collect_time,
statistics);
},
}
}
});
}
Ok(())
}.boxed()
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Transaction2PcAllConflicts for MetaTabTr<C, Log> {
fn precheck_all_conflicts(&self)
-> BoxFuture<'_, Result<(), <Self as Transaction2Pc>::PrepareError>> {
let tr = self.clone();
async move {
tr.precheck_versions().await
}.boxed()
}
fn prepare_all_conflicts(&self)
-> BoxFuture<'_, Result<Option<<Self as Transaction2Pc>::PrepareOutput>, <Self as Transaction2Pc>::PrepareError>> {
let tr = self.clone();
async move {
tr.prepare_registered(PrepareConflictKind::All).await
}.boxed()
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> UnitTransaction for MetaTabTr<C, Log> {
type Status = Transaction2PcStatus;
type Qos = TableTrQos;
fn is_unit(&self) -> bool {
true
}
fn get_status(&self) -> <Self as UnitTransaction>::Status {
self.0.status.lock().clone()
}
fn set_status(&self, status: <Self as UnitTransaction>::Status) {
*self.0.status.lock() = status;
}
fn qos(&self) -> <Self as UnitTransaction>::Qos {
if self.is_require_persistence() {
TableTrQos::Safe
} else {
TableTrQos::ThreadSafe
}
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> SequenceTransaction for MetaTabTr<C, Log> {
type Item = Self;
fn is_sequence(&self) -> bool {
false
}
fn prev_item(&self) -> Option<<Self as SequenceTransaction>::Item> {
None
}
fn next_item(&self) -> Option<<Self as SequenceTransaction>::Item> {
None
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> TransactionTree for MetaTabTr<C, Log> {
type Node = KVDBTransaction<C, Log>;
type NodeInterator = KVDBChildTrList<C, Log>;
fn is_tree(&self) -> bool {
false
}
fn children_len(&self) -> usize {
0
}
fn to_children(&self) -> Self::NodeInterator {
KVDBChildTrList::new()
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> KVAction for MetaTabTr<C, Log> {
type Key = Binary;
type Value = Binary;
type Error = KVTableTrError;
fn dirty_query(&self, key: <Self as KVAction>::Key)
-> BoxFuture<Option<<Self as KVAction>::Value>> {
let tr = self.clone();
async move {
if let Some(value) = tr.0.root_mut.lock().get(&key) {
return Some(value.clone());
}
None
}.boxed()
}
fn query(&self, key: <Self as KVAction>::Key)
-> BoxFuture<Option<<Self as KVAction>::Value>> {
let tr = self.clone();
async move {
let mut actions_locked = tr.0.actions.lock();
if let None = actions_locked.get(&key) {
let _ = actions_locked.insert(key.clone(), KVActionLog::Read);
}
if let Some(value) = tr.0.root_mut.lock().get(&key) {
return Some(value.clone());
}
None
}.boxed()
}
fn dirty_upsert(&self,
key: <Self as KVAction>::Key,
value: <Self as KVAction>::Value)
-> BoxFuture<Result<(), <Self as KVAction>::Error>> {
let tr = self.clone();
async move {
let _ = tr.0.actions.lock().insert(key.clone(), KVActionLog::DirtyWrite(Some(value.clone())));
let _ = tr.0.root_mut.lock().upsert(key, value, false);
Ok(())
}.boxed()
}
fn upsert(&self,
key: <Self as KVAction>::Key,
value: <Self as KVAction>::Value)
-> BoxFuture<Result<(), <Self as KVAction>::Error>> {
let tr = self.clone();
async move {
let _ = tr.0.actions.lock().insert(key.clone(), KVActionLog::Write(Some(value.clone())));
let _ = tr.0.root_mut.lock().upsert(key, value, false);
Ok(())
}.boxed()
}
fn dirty_delete(&self, key: <Self as KVAction>::Key)
-> BoxFuture<Result<Option<<Self as KVAction>::Value>, <Self as KVAction>::Error>> {
let tr = self.clone();
async move {
let _ = tr.0.actions.lock().insert(key.clone(), KVActionLog::DirtyWrite(None));
if let Some(Some(value)) = tr.0.root_mut.lock().delete(&key, false) {
return Ok(Some(value));
}
Ok(None)
}.boxed()
}
fn delete(&self, key: <Self as KVAction>::Key)
-> BoxFuture<Result<Option<<Self as KVAction>::Value>, <Self as KVAction>::Error>> {
let tr = self.clone();
async move {
let _ = tr.0.actions.lock().insert(key.clone(), KVActionLog::Write(None));
if let Some(Some(value)) = tr.0.root_mut.lock().delete(&key, false) {
return Ok(Some(value));
}
Ok(None)
}.boxed()
}
fn keys<'a>(&self,
key: Option<<Self as KVAction>::Key>,
descending: bool)
-> BoxStream<'a, <Self as KVAction>::Key> {
let root = self.0.root_mut.lock().clone();
let mut iterator = OrdMapSnapshot::new(root, key.as_ref(), descending);
let stream = stream! {
while let Some(key) = iterator.next_key() {
yield key;
}
};
stream.boxed()
}
fn values<'a>(&self,
key: Option<<Self as KVAction>::Key>,
descending: bool)
-> BoxStream<'a, (<Self as KVAction>::Key, <Self as KVAction>::Value)> {
let root = self.0.root_mut.lock().clone();
let mut iterator = OrdMapSnapshot::new(root, key.as_ref(), descending);
let stream = stream! {
while let Some((key, value)) = iterator.next_entry() {
yield (key, value);
}
};
stream.boxed()
}
fn lock_key(&self, _key: <Self as KVAction>::Key)
-> BoxFuture<Result<(), <Self as KVAction>::Error>> {
async move {
Ok(())
}.boxed()
}
fn unlock_key(&self, _key: <Self as KVAction>::Key)
-> BoxFuture<Result<(), <Self as KVAction>::Error>> {
async move {
Ok(())
}.boxed()
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> MetaTabTr<C, Log> {
#[inline]
fn new(source: Atom,
is_writable: bool,
is_persistent: bool,
prepare_timeout: u64,
commit_timeout: u64,
table: MetaTable<C, Log>) -> Self {
let root_ref = table.0.root.lock().clone();
let inner = InnerMetaTabTr {
source,
tid: SpinLock::new(None),
cid: SpinLock::new(None),
status: SpinLock::new(Transaction2PcStatus::default()),
writable: is_writable,
persistence: AtomicBool::new(is_persistent),
prepare_timeout,
commit_timeout,
root_mut: SpinLock::new(root_ref.clone()),
root_ref,
table,
actions: SpinLock::new(XHashMap::default()),
version_context: None,
};
MetaTabTr(Arc::new(inner))
}
pub(crate) fn new_managed(source: Atom,
is_writable: bool,
is_persistent: bool,
prepare_timeout: u64,
commit_timeout: u64,
table: MetaTable<C, Log>,
versions: KeyVersions,
mode: PrepareMode,
expected: XHashMap<Binary, Version>,
receipt: Option<VersionReceipt>,
actions: XHashMap<Binary, KVActionLog>) -> Self {
let root_locked = table.0.root.lock();
let root_ref = root_locked.clone();
let snapshot = versions.lease_current();
drop(root_locked);
let mut root_mut = root_ref.clone();
for (key, action) in &actions {
match action {
KVActionLog::Write(Some(value)) | KVActionLog::DirtyWrite(Some(value)) => {
let _ = root_mut.upsert(key.clone(), value.clone(), false);
},
KVActionLog::Write(None) | KVActionLog::DirtyWrite(None) => {
let _ = root_mut.delete(key, false);
},
KVActionLog::Read => (),
}
}
let version_context = TableVersionContext::new(versions,
snapshot,
mode,
expected,
receipt);
let inner = InnerMetaTabTr {
source,
tid: SpinLock::new(None),
cid: SpinLock::new(None),
status: SpinLock::new(Transaction2PcStatus::default()),
writable: is_writable,
persistence: AtomicBool::new(is_persistent),
prepare_timeout,
commit_timeout,
root_mut: SpinLock::new(root_mut),
root_ref,
table,
actions: SpinLock::new(actions),
version_context: Some(version_context),
};
MetaTabTr(Arc::new(inner))
}
async fn precheck_versions(&self) -> Result<(), KVTableTrError> {
let Some(context) = self.0.version_context.as_ref() else {
return Ok(());
};
if context.mode() != PrepareMode::Versioned {
return Ok(());
}
let _publication = context.versions().publication().read().await;
let mut conflicts = Vec::new();
for (key, expected) in context.expected() {
if context.versions().current_version(key).as_ref() != Some(expected) {
conflicts.push(TableKey {
table: self.0.table.name(),
key: key.clone(),
});
}
}
if conflicts.is_empty() {
Ok(())
} else {
Err(KVTableTrError::new_all_conflicts_error(conflicts))
}
}
async fn prepare_registered(&self,
conflict_kind: PrepareConflictKind)
-> Result<Option<Vec<u8>>, KVTableTrError> {
if !self.is_writable() {
return Ok(None);
}
let _publication = match self.0.version_context.as_ref() {
Some(context) => Some(context.versions().publication().read().await),
None => None,
};
let actions = self.0.actions.lock().clone();
let mode = self
.0
.version_context
.as_ref()
.map(TableVersionContext::mode)
.unwrap_or(PrepareMode::Ordinary);
let mut conflict_keys = Vec::new();
if let Some(context) = self.0.version_context.as_ref() {
if context.mode() == PrepareMode::Versioned {
for (key, expected) in context.expected() {
if context.versions().current_version(key).as_ref() != Some(expected) {
conflict_keys.push(key.clone());
}
}
}
}
let current_root = self.0.table.0.root.lock().clone();
for (key, action) in &actions {
if action.is_dirty_writed() {
continue;
}
if let Some(context) = self.0.version_context.as_ref() {
if context
.versions()
.has_committed_after(key, context.snapshot_revision()) {
conflict_keys.push(key.clone());
continue;
}
}
if !binary_state_equal(self.0.root_ref.get(key), current_root.get(key)) {
conflict_keys.push(key.clone());
}
}
let write_buf = self.prepare_output(&actions);
let mut prepare = self.0.table.0.prepare.lock();
for (key, action) in &actions {
if has_prepared_conflict(&prepare, key, mode, action) {
conflict_keys.push(key.clone());
}
}
if !conflict_keys.is_empty() {
return Err(self.prepare_conflict_error(conflict_kind, conflict_keys));
}
let _ = mem::replace(&mut *self.0.actions.lock(), XHashMap::default());
prepare.insert(self.get_transaction_uid().unwrap(), PreparedActions {
mode,
actions,
});
Ok(write_buf)
}
fn prepare_output(&self,
actions: &XHashMap<Binary, KVActionLog>) -> Option<Vec<u8>> {
let writed_count = actions
.values()
.filter(|action| matches!(action,
KVActionLog::Write(_) | KVActionLog::DirtyWrite(_)))
.count() as u64;
if writed_count == 0 {
return None;
}
let mut buf = Vec::new();
self.0.table.init_table_prepare_output(&mut buf, writed_count);
for (key, action) in actions {
match action {
KVActionLog::Write(None) | KVActionLog::DirtyWrite(None) => {
self.0.table.append_key_value_to_table_prepare_output(&mut buf, key, None);
},
KVActionLog::Write(Some(value)) | KVActionLog::DirtyWrite(Some(value)) => {
self.0.table.append_key_value_to_table_prepare_output(&mut buf,
key,
Some(value));
},
KVActionLog::Read => (),
}
}
Some(buf)
}
fn prepare_conflict_error(&self,
conflict_kind: PrepareConflictKind,
keys: Vec<Binary>) -> KVTableTrError {
let key = keys[0].clone();
match conflict_kind {
PrepareConflictKind::Common => {
KVTableTrError::new_transaction_error(
ErrorLevel::Normal,
format!("Prepare meta table conflicted, table: {:?}, key: {:?}, source: {:?}, transaction_uid: {:?}, reason: committed state or prepared reservation changed",
self.0.table.name().as_str(),
key,
self.0.source,
self.get_transaction_uid()))
},
PrepareConflictKind::First => {
KVTableTrError::new_conflicts_error(self.0.table.name(), key)
},
PrepareConflictKind::All => {
KVTableTrError::new_all_conflicts_error(keys
.into_iter()
.map(|key| TableKey {
table: self.0.table.name(),
key,
})
.collect())
},
}
}
pub(crate) fn prepare_repair(&self, transaction_uid: Guid) {
let actions = mem::replace(&mut *self.0.actions.lock(), XHashMap::default());
for (key, action) in &actions {
match action {
KVActionLog::Write(Some(value)) | KVActionLog::DirtyWrite(Some(value)) => {
self
.0
.table
.0
.root
.lock()
.upsert(key.clone(), value.clone(), false);
},
KVActionLog::Write(None) | KVActionLog::DirtyWrite(None) => {
self.0.table.0.root.lock().delete(key, false);
},
KVActionLog::Read => (), }
}
self.0.table.0.prepare.lock().insert(transaction_uid, PreparedActions {
mode: PrepareMode::Ordinary,
actions,
});
}
}
struct InnerMetaTabTr<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> {
source: Atom, tid: SpinLock<Option<Guid>>, cid: SpinLock<Option<Guid>>, status: SpinLock<Transaction2PcStatus>, writable: bool, persistence: AtomicBool, prepare_timeout: u64, commit_timeout: u64, root_mut: SpinLock<OrdMap<Tree<Binary, Binary>>>, root_ref: OrdMap<Tree<Binary, Binary>>, table: MetaTable<C, Log>, actions: SpinLock<XHashMap<Binary, KVActionLog>>, version_context: Option<TableVersionContext>, }
struct MetaTableLoader<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> {
statistics: XHashMap<PathBuf, (u64, u64)>, removed: XHashMap<Vec<u8>, ()>, table: MetaTable<C, Log>, }
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> PairLoader for MetaTableLoader<C, Log> {
fn is_require(&self, _log_file: Option<&PathBuf>, key: &Vec<u8>) -> bool {
!self
.removed
.contains_key(key)
&&
self
.table
.0
.root
.lock()
.get(&Binary::new(key.clone()))
.is_none()
}
fn load(&mut self,
log_file: Option<&PathBuf>,
_method: LogMethod,
key: Vec<u8>,
value: Option<Vec<u8>>) {
if let Some(value) = value {
if let Some(path) = log_file {
match self.statistics.entry(path.clone()) {
HashMapEntry::Occupied(mut o) => {
let statistics = o.get_mut();
statistics.0 += 1;
statistics.1 += (key.len() + value.len()) as u64;
},
HashMapEntry::Vacant(v) => {
v.insert((1, (key.len() + value.len()) as u64));
},
}
}
self.table.0.root.lock().insert(Binary::new(key), Binary::new(value));
} else {
self.removed.insert(key, ());
}
}
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> MetaTableLoader<C, Log> {
pub fn new(table: MetaTable<C, Log>) -> Self {
MetaTableLoader {
statistics: XHashMap::default(),
removed: XHashMap::default(),
table,
}
}
pub fn log_files_len(&self) -> usize {
self.statistics.len()
}
pub fn keys_len(&self) -> u64 {
let mut len = 0;
for statistics in self.statistics.values() {
len += statistics.0;
}
len
}
pub fn bytes_len(&self) -> u64 {
let mut len = 0;
for statistics in self.statistics.values() {
len += statistics.1;
}
len
}
}
async fn collect_waits<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
>(table: &MetaTable<C, Log>,
timeout: Option<usize>) -> Result<(Duration, (usize, usize, usize)), (Duration, (usize, usize, usize))> {
if let Some(timeout) = timeout {
table.0.rt.timeout(timeout).await;
}
if let Err(_) = table.0.collecting.compare_exchange(false,
true,
Ordering::Acquire,
Ordering::Relaxed) {
return Ok((Instant::now().elapsed(), (0, 0, 0)));
}
let mut waits = VecDeque::new();
let mut log_uid = 0;
let mut trs_len = 0;
let mut keys_len = 0;
let mut bytes_len = 0;
let now = Instant::now();
{
let mut locked = table
.0
.waits
.lock()
.await;
while let Some((wait_tr, actions, confirm)) = locked.pop_front() {
for (key, actions) in actions.iter() {
match actions {
KVActionLog::Write(None) | KVActionLog::DirtyWrite(None) => {
log_uid = table
.0
.log_file
.append(LogMethod::Remove,
key.as_ref(),
&[]);
keys_len += 1;
bytes_len += key.len();
},
KVActionLog::Write(Some(value)) | KVActionLog::DirtyWrite(Some(value)) => {
log_uid = table
.0
.log_file
.append(LogMethod::PlainAppend,
key.as_ref(),
value.as_ref());
keys_len += 1;
bytes_len += key.len() + value.len();
},
KVActionLog::Read => (), }
}
trs_len += 1;
waits.push_back((wait_tr, confirm));
}
if let Err(e) = table
.0
.log_file
.delay_commit(log_uid,
false,
DEFAULT_LOG_FILE_COMMIT_DELAY_TIMEOUT)
.await {
table.0.collecting.store(false, Ordering::Release); error!("Collect meta table failed, table: {:?}, transactions: {}, keys: {}, bytes: {}, reason: {:?}",
table.name().as_str(),
trs_len,
keys_len,
bytes_len,
e);
return Err((now.elapsed(), (trs_len, keys_len, bytes_len)));
}
}
if let Some(notifier) = table.0.notifier.as_ref() {
for (wait_tr, confirm) in waits {
if let Err(e) = confirm(wait_tr.get_transaction_uid().unwrap(),
wait_tr.get_commit_uid().unwrap(),
Ok(())) {
notifier.send(KVDBEvent::CommitFailed(wait_tr.get_source(),
wait_tr.0.table.name(),
KVDBTableType::BtreeOrdTab,
wait_tr.get_transaction_uid().unwrap(),
wait_tr.get_commit_uid().unwrap()))
.await;
error!("Collect meta table failed, table: {:?}, transactions: {}, keys: {}, bytes: {}, reason: {:?}",
table.name().as_str(),
trs_len,
keys_len,
bytes_len,
e);
} else {
notifier.send(KVDBEvent::ConfirmCommited(wait_tr.get_source(),
wait_tr.0.table.name(),
KVDBTableType::BtreeOrdTab,
wait_tr.get_transaction_uid().unwrap(),
wait_tr.get_commit_uid().unwrap()))
.await;
}
}
} else {
for (wait_tr, confirm) in waits {
if let Err(e) = confirm(wait_tr.get_transaction_uid().unwrap(),
wait_tr.get_commit_uid().unwrap(),
Ok(())) {
error!("Collect meta table failed, table: {:?}, transactions: {}, keys: {}, bytes: {}, reason: {:?}",
table.name().as_str(),
trs_len,
keys_len,
bytes_len,
e);
}
}
}
table.0.collecting.store(false, Ordering::Release);
Ok((now.elapsed(), (trs_len, keys_len, bytes_len)))
}