use aptos_infallible::Mutex;
use crossbeam::utils::CachePadded;
use std::{
cmp::min,
hint,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Condvar,
},
};
pub type TxnIndex = usize;
pub type Incarnation = usize;
pub type Version = (TxnIndex, Incarnation);
type DependencyCondvar = Arc<(Mutex<bool>, Condvar)>;
pub struct TaskGuard<'a> {
counter: &'a AtomicUsize,
}
impl<'a> TaskGuard<'a> {
pub fn new(counter: &'a AtomicUsize) -> Self {
counter.fetch_add(1, Ordering::SeqCst);
Self { counter }
}
}
impl Drop for TaskGuard<'_> {
fn drop(&mut self) {
assert!(self.counter.fetch_sub(1, Ordering::SeqCst) > 0);
}
}
pub enum SchedulerTask<'a> {
ExecutionTask(Version, Option<DependencyCondvar>, TaskGuard<'a>),
ValidationTask(Version, TaskGuard<'a>),
NoTask,
Done,
}
#[derive(Debug)]
enum TransactionStatus {
ReadyToExecute(Incarnation, Option<DependencyCondvar>),
Executing(Incarnation),
Suspended(Incarnation, DependencyCondvar),
Executed(Incarnation),
Aborting(Incarnation),
}
impl PartialEq for TransactionStatus {
fn eq(&self, other: &Self) -> bool {
use TransactionStatus::*;
match (self, other) {
(&ReadyToExecute(ref a, _), &ReadyToExecute(ref b, _))
| (&Executing(ref a), &Executing(ref b))
| (&Suspended(ref a, _), &Suspended(ref b, _))
| (&Executed(ref a), &Executed(ref b))
| (&Aborting(ref a), &Aborting(ref b)) => a == b,
_ => false,
}
}
}
pub struct Scheduler {
num_txns: usize,
execution_idx: AtomicUsize,
validation_idx: AtomicUsize,
decrease_cnt: AtomicUsize,
num_active_tasks: AtomicUsize,
done_marker: AtomicBool,
txn_dependency: Vec<CachePadded<Mutex<Vec<TxnIndex>>>>,
txn_status: Vec<CachePadded<Mutex<TransactionStatus>>>,
}
impl Scheduler {
pub fn new(num_txns: usize) -> Self {
Self {
num_txns,
execution_idx: AtomicUsize::new(0),
validation_idx: AtomicUsize::new(0),
decrease_cnt: AtomicUsize::new(0),
num_active_tasks: AtomicUsize::new(0),
done_marker: AtomicBool::new(false),
txn_dependency: (0..num_txns)
.map(|_| CachePadded::new(Mutex::new(Vec::new())))
.collect(),
txn_status: (0..num_txns)
.map(|_| CachePadded::new(Mutex::new(TransactionStatus::ReadyToExecute(0, None))))
.collect(),
}
}
pub fn num_txn_to_execute(&self) -> usize {
self.num_txns
}
pub fn try_abort(&self, txn_idx: TxnIndex, incarnation: Incarnation) -> bool {
let mut status = self.txn_status[txn_idx].lock();
if *status == TransactionStatus::Executed(incarnation) {
*status = TransactionStatus::Aborting(incarnation);
true
} else {
false
}
}
pub fn next_task(&self) -> SchedulerTask {
loop {
if self.done() {
return SchedulerTask::Done;
}
let idx_to_validate = self.validation_idx.load(Ordering::SeqCst);
let idx_to_execute = self.execution_idx.load(Ordering::SeqCst);
if idx_to_validate < idx_to_execute {
if let Some((version_to_validate, guard)) = self.try_validate_next_version() {
return SchedulerTask::ValidationTask(version_to_validate, guard);
}
} else if let Some((version_to_execute, maybe_condvar, guard)) =
self.try_execute_next_version()
{
return SchedulerTask::ExecutionTask(version_to_execute, maybe_condvar, guard);
}
}
}
pub fn wait_for_dependency(
&self,
txn_idx: TxnIndex,
dep_txn_idx: TxnIndex,
) -> Option<DependencyCondvar> {
let dep_condvar = Arc::new((Mutex::new(false), Condvar::new()));
let mut stored_deps = self.txn_dependency[dep_txn_idx].lock();
{
if self.is_executed(dep_txn_idx).is_some() {
return None;
}
self.suspend(txn_idx, dep_condvar.clone());
stored_deps.push(txn_idx);
}
Some(dep_condvar)
}
pub fn finish_execution<'a>(
&self,
txn_idx: TxnIndex,
incarnation: Incarnation,
revalidate_suffix: bool,
guard: TaskGuard<'a>,
) -> SchedulerTask<'a> {
self.set_executed_status(txn_idx, incarnation);
let txn_deps: Vec<TxnIndex> = {
let mut stored_deps = self.txn_dependency[txn_idx].lock();
std::mem::take(&mut stored_deps)
};
let min_dep = txn_deps
.into_iter()
.map(|dep| {
self.resume(dep);
dep
})
.min();
if let Some(execution_target_idx) = min_dep {
self.decrease_execution_idx(execution_target_idx);
}
if self.validation_idx.load(Ordering::SeqCst) > txn_idx {
if revalidate_suffix {
self.decrease_validation_idx(txn_idx);
} else {
return SchedulerTask::ValidationTask((txn_idx, incarnation), guard);
}
}
SchedulerTask::NoTask
}
pub fn finish_abort<'a>(
&self,
txn_idx: TxnIndex,
incarnation: Incarnation,
guard: TaskGuard<'a>,
) -> SchedulerTask<'a> {
self.set_aborted_status(txn_idx, incarnation);
self.decrease_validation_idx(txn_idx + 1);
if self.execution_idx.load(Ordering::SeqCst) > txn_idx {
if let Some((new_incarnation, maybe_condvar)) = self.try_incarnate(txn_idx) {
return SchedulerTask::ExecutionTask(
(txn_idx, new_incarnation),
maybe_condvar,
guard,
);
}
}
SchedulerTask::NoTask
}
}
impl Scheduler {
fn decrease_validation_idx(&self, target_idx: TxnIndex) {
if self.validation_idx.fetch_min(target_idx, Ordering::SeqCst) > target_idx {
self.decrease_cnt.fetch_add(1, Ordering::SeqCst);
}
}
fn decrease_execution_idx(&self, target_idx: TxnIndex) {
if self.execution_idx.fetch_min(target_idx, Ordering::SeqCst) > target_idx {
self.decrease_cnt.fetch_add(1, Ordering::SeqCst);
}
}
fn try_incarnate(&self, txn_idx: TxnIndex) -> Option<(Incarnation, Option<DependencyCondvar>)> {
if txn_idx >= self.txn_status.len() {
return None;
}
let mut status = self.txn_status[txn_idx].lock();
if let TransactionStatus::ReadyToExecute(incarnation, maybe_condvar) = &*status {
let ret = (*incarnation, maybe_condvar.clone());
*status = TransactionStatus::Executing(*incarnation);
Some(ret)
} else {
None
}
}
fn is_executed(&self, txn_idx: TxnIndex) -> Option<Incarnation> {
if txn_idx >= self.txn_status.len() {
return None;
}
let status = self.txn_status[txn_idx].lock();
if let TransactionStatus::Executed(incarnation) = *status {
Some(incarnation)
} else {
None
}
}
fn try_validate_next_version(&self) -> Option<(Version, TaskGuard)> {
let idx_to_validate = self.validation_idx.load(Ordering::SeqCst);
if idx_to_validate >= self.num_txns {
if !self.check_done() {
hint::spin_loop();
}
return None;
}
let guard = TaskGuard::new(&self.num_active_tasks);
let idx_to_validate = self.validation_idx.fetch_add(1, Ordering::SeqCst);
self.is_executed(idx_to_validate)
.map(|incarnation| ((idx_to_validate, incarnation), guard))
}
fn try_execute_next_version(&self) -> Option<(Version, Option<DependencyCondvar>, TaskGuard)> {
let idx_to_execute = self.execution_idx.load(Ordering::SeqCst);
if idx_to_execute >= self.num_txns {
if !self.check_done() {
hint::spin_loop();
}
return None;
}
let guard = TaskGuard::new(&self.num_active_tasks);
let idx_to_execute = self.execution_idx.fetch_add(1, Ordering::SeqCst);
self.try_incarnate(idx_to_execute)
.map(|(incarnation, maybe_condvar)| {
((idx_to_execute, incarnation), maybe_condvar, guard)
})
}
fn suspend(&self, txn_idx: TxnIndex, dep_condvar: DependencyCondvar) {
let mut status = self.txn_status[txn_idx].lock();
if let TransactionStatus::Executing(incarnation) = *status {
*status = TransactionStatus::Suspended(incarnation, dep_condvar);
} else {
unreachable!();
}
}
fn resume(&self, txn_idx: TxnIndex) {
let mut status = self.txn_status[txn_idx].lock();
if let TransactionStatus::Suspended(incarnation, dep_condvar) = &*status {
*status = TransactionStatus::ReadyToExecute(*incarnation, Some(dep_condvar.clone()));
} else {
unreachable!();
}
}
fn set_executed_status(&self, txn_idx: TxnIndex, incarnation: Incarnation) {
let mut status = self.txn_status[txn_idx].lock();
debug_assert!(*status == TransactionStatus::Executing(incarnation));
*status = TransactionStatus::Executed(incarnation);
}
fn set_aborted_status(&self, txn_idx: TxnIndex, incarnation: Incarnation) {
let mut status = self.txn_status[txn_idx].lock();
debug_assert!(*status == TransactionStatus::Aborting(incarnation));
*status = TransactionStatus::ReadyToExecute(incarnation + 1, None);
}
fn check_done(&self) -> bool {
let observed_cnt = self.decrease_cnt.load(Ordering::SeqCst);
let val_idx = self.validation_idx.load(Ordering::SeqCst);
let exec_idx = self.execution_idx.load(Ordering::SeqCst);
let num_tasks = self.num_active_tasks.load(Ordering::SeqCst);
if min(exec_idx, val_idx) < self.num_txns || num_tasks > 0 {
return false;
}
if observed_cnt == self.decrease_cnt.load(Ordering::SeqCst) {
self.done_marker.store(true, Ordering::Release);
true
} else {
false
}
}
fn done(&self) -> bool {
self.done_marker.load(Ordering::Acquire)
}
}