use std::mem;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::sync::{Arc, atomic::{AtomicBool, AtomicUsize, Ordering}};
use std::collections::hash_map::Entry as HashMapEntry;
use parking_lot::Mutex;
use futures::{future::{FutureExt, BoxFuture}, stream::{StreamExt, BoxStream}};
use async_lock::Mutex as AsyncMutex;
use async_stream::stream;
use log::{debug, info, error};
use pi_atom::Atom;
use pi_guid::Guid;
use pi_hash::XHashMap;
use pi_ordmap::{ordmap::{ImOrdMap, Iter, OrdMap},
asbtree::Tree};
use pi_async_rt::{lock::spin_lock::SpinLock,
rt::{AsyncRuntime, multi_thread::MultiTaskRuntime}};
use pi_async_transaction::{AsyncTransaction,
Transaction2Pc,
Transaction2PcAllConflicts,
UnitTransaction,
SequenceTransaction,
TransactionTree,
TransactionError,
AsyncCommitLog,
ErrorLevel,
manager_2pc::Transaction2PcStatus};
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};
use std::collections::VecDeque;
const DEFAULT_LOG_FILE_COMMIT_DELAY_TIMEOUT: usize = 1000;
#[derive(Clone)]
pub struct LogWriteTable<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
>(Arc<InnerLogWriteTable<C, Log>>);
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Send for LogWriteTable<C, Log> {}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Sync for LogWriteTable<C, Log> {}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> KVTable for LogWriteTable<C, Log> {
type Name = Atom;
type Tr = LogWTabTr<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 {
LogWTabTr::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 only writable table failed, path: {:?}, table: {:?}, reason: {:?}", table.0.log_file.path(), table.0.name.as_str(), e)));
},
Ok(writed_log_index) => {
info!("Ready collect only writable table ok, 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 only writable table failed, path: {:?}, table: {:?}, reason: {:?}", table.0.log_file.path(), table.0.name.as_str(), e)));
},
Ok((size, len)) => {
info!("Collect only writable table ok, 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>,
> LogWriteTable<C, Log> {
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) -> 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 only writable 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 = InnerLogWriteTable {
name: name.clone(),
root,
prepare,
rt,
waits,
waits_size,
waits_limit,
wait_timeout,
collecting,
log_file,
};
let table = LogWriteTable(Arc::new(inner));
let now = Instant::now();
let mut loader = LogWriteTableLoader::new(table.clone());
if let Err(e) = table.0.log_file.load(&mut loader,
None,
load_buf_len,
is_checksum).await {
panic!("Load only writable table failed, table: {:?}, path: {:?}, reason: {:?}",
name.as_str(),
path.as_ref(),
e);
}
info!("Load only writable table ok, 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 only writable table failed, table: {:?}, time: {:?}, statistics: {:?}, reason: out of time",
table_copy.name().as_str(),
collect_time,
statistics);
},
Ok((collect_time, statistics)) => {
debug!("Collect only writable table ok, table: {:?}, time: {:?}, statistics: {:?}, reason: out of time",
table_copy.name().as_str(),
collect_time,
statistics);
},
}
}
});
table
},
}
}
}
struct InnerLogWriteTable<
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<(LogWTabTr<C, Log>, XHashMap<Binary, KVActionLog>, <LogWTabTr<C, Log> as Transaction2Pc>::CommitConfirm)>>, waits_size: AtomicUsize, waits_limit: usize, wait_timeout: usize, collecting: AtomicBool, log_file: LogFile, }
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Send for InnerLogWriteTable<C, Log> {}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Sync for InnerLogWriteTable<C, Log> {}
#[derive(Clone)]
pub struct LogWTabTr<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
>(Arc<InnerLogWTabTr<C, Log>>);
#[derive(Clone, Copy)]
enum PrepareConflictKind {
Common,
First,
All,
}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Send for LogWTabTr<C, Log> {}
unsafe impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> Sync for LogWTabTr<C, Log> {}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> AsyncTransaction for LogWTabTr<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 LogWTabTr<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 only writable 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 only writable table failed, table: {:?}, time: {:?}, statistics: {:?}, reason: out of size",
table_copy.name().as_str(),
collect_time,
statistics);
},
Ok((collect_time, statistics)) => {
info!("Collect only writable table ok, 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 LogWTabTr<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 LogWTabTr<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 LogWTabTr<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 LogWTabTr<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 LogWTabTr<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>> {
async move {
None
}.boxed()
}
fn query(&self, _key: <Self as KVAction>::Key)
-> BoxFuture<Option<<Self as KVAction>::Value>> {
async move {
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>> {
async move {
Ok(None)
}.boxed()
}
fn delete(&self, _key: <Self as KVAction>::Key)
-> BoxFuture<Result<Option<<Self as KVAction>::Value>, <Self as KVAction>::Error>> {
async move {
Ok(None)
}.boxed()
}
fn keys<'a>(&self,
key: Option<<Self as KVAction>::Key>,
descending: bool)
-> BoxStream<'a, <Self as KVAction>::Key> {
let stream = stream! {
yield Binary::new(vec![]);
};
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 stream = stream! {
yield (Binary::new(vec![]), Binary::new(vec![]));
};
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>,
> LogWTabTr<C, Log> {
#[inline]
fn new(source: Atom,
is_writable: bool,
is_persistent: bool,
prepare_timeout: u64,
commit_timeout: u64,
table: LogWriteTable<C, Log>) -> Self {
let root_ref = table.0.root.lock().clone();
let inner = InnerLogWTabTr {
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,
};
LogWTabTr(Arc::new(inner))
}
pub(crate) fn new_managed(source: Atom,
is_writable: bool,
is_persistent: bool,
prepare_timeout: u64,
commit_timeout: u64,
table: LogWriteTable<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 = InnerLogWTabTr {
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),
};
LogWTabTr(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(Some(_))
| KVActionLog::DirtyWrite(Some(_))))
.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(Some(value)) | KVActionLog::DirtyWrite(Some(value)) => {
self.0.table.append_key_value_to_table_prepare_output(&mut buf,
key,
Some(value));
},
_ => (),
}
}
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 only writable 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 InnerLogWTabTr<
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: LogWriteTable<C, Log>,
actions: SpinLock<XHashMap<Binary, KVActionLog>>,
version_context: Option<TableVersionContext>,
}
struct LogWriteTableLoader<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> {
statistics: XHashMap<PathBuf, (u64, u64)>,
removed: XHashMap<Vec<u8>, ()>,
table: LogWriteTable<C, Log>,
}
impl<
C: Clone + Send + 'static,
Log: AsyncCommitLog<C = C, Cid = Guid>,
> PairLoader for LogWriteTableLoader<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>,
> LogWriteTableLoader<C, Log> {
pub fn new(table: LogWriteTable<C, Log>) -> Self {
LogWriteTableLoader {
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: &LogWriteTable<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 only writable 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)));
}
}
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 only writable 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)))
}