use std::{
ptr::NonNull,
sync::{
Arc, RwLock,
atomic::{AtomicU64, Ordering},
},
};
use super::super::task::TaskMetadata;
use super::directory::{BlockDirectory, BlockLookup};
use super::retention::RetentionPolicy;
use super::state::{TaskState, TaskStateBlock, task_location};
use super::token::{SchedulerStateLease, TaskLifecycleToken};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CancelOutcome {
Requested,
AlreadyCompleted,
}
enum Observation<R> {
Retired,
Unregistered,
Registered(R),
}
#[derive(Debug)]
pub(super) struct IssuedId(u64);
impl IssuedId {
pub(super) const fn get(&self) -> u64 {
self.0
}
pub(super) fn location(&self) -> (usize, usize) {
task_location(self.0)
}
}
#[derive(Debug)]
pub struct TaskRegistry {
pub(super) blocks: RwLock<BlockDirectory>,
pub(super) next_id: AtomicU64,
pub(super) retention: Option<RetentionPolicy>,
}
impl TaskRegistry {
#[must_use]
pub const fn new() -> Self {
Self {
blocks: RwLock::new(BlockDirectory::new()),
next_id: AtomicU64::new(1),
retention: None,
}
}
#[must_use]
pub fn with_retention(policy: RetentionPolicy) -> Self {
Self {
retention: Some(policy),
..Self::new()
}
}
pub(super) fn issue_id(&self) -> IssuedId {
IssuedId(self.next_id.fetch_add(1, Ordering::Relaxed))
}
pub fn register_task(&self) -> u64 {
let id = self.issue_id();
let task_id = id.get();
self.register_owned(id).release();
task_id
}
#[cfg(any(test, feature = "registry-diagnostics"))]
pub(crate) fn register_next_task(&self) -> (u64, TaskLifecycleToken) {
let id = self.issue_id();
let task_id = id.get();
(task_id, self.register_owned(id))
}
pub(crate) unsafe fn register_next_scheduled_task(
&self,
) -> (u64, TaskLifecycleToken<SchedulerStateLease>) {
let id = self.issue_id();
let task_id = id.get();
let state = self.insert_slot(id);
(
task_id,
unsafe { TaskLifecycleToken::new_scheduled(state) },
)
}
pub(super) fn register_owned(&self, id: IssuedId) -> TaskLifecycleToken {
let (block_index, slot_index) = id.location();
let block = self.ensure_block(block_index);
let state = unsafe { block.insert(slot_index) };
TaskLifecycleToken::new_owned(block, state)
}
fn insert_slot(&self, id: IssuedId) -> NonNull<TaskState> {
let (block_index, slot_index) = id.location();
{
let blocks = self
.blocks
.read()
.expect("task registry block directory is never poisoned");
if let BlockLookup::Live(block) = blocks.lookup(block_index) {
return unsafe { block.insert(slot_index) };
}
}
let block = self.ensure_block(block_index);
unsafe { block.insert(slot_index) }
}
pub fn mark_started(&self, task_id: u64, worker_id: usize) {
self.with_state(task_id, |state| {
state.mark_started(worker_id);
});
}
pub fn mark_completed(&self, task_id: u64) {
self.with_state(task_id, TaskState::mark_completed);
}
#[must_use]
pub fn is_completed(&self, task_id: u64) -> bool {
self.completion(task_id) == Some(true)
}
#[must_use]
pub fn completion(&self, task_id: u64) -> Option<bool> {
match self.observe(task_id, TaskState::is_completed) {
Observation::Retired => Some(true),
Observation::Unregistered => None,
Observation::Registered(completed) => Some(completed),
}
}
#[must_use]
pub fn get_metadata(&self, task_id: u64) -> Option<TaskMetadata> {
self.with_state(task_id, |state| state.snapshot(task_id))
}
#[must_use]
pub fn active_count(&self) -> usize {
self.blocks
.read()
.expect("task registry block directory is never poisoned")
.resident_blocks()
.flat_map(|block| block.states())
.filter(|state| !state.is_completed())
.count()
}
#[must_use]
pub fn completed_count(&self) -> usize {
self.blocks
.read()
.expect("task registry block directory is never poisoned")
.resident_blocks()
.flat_map(|block| block.states())
.filter(|state| state.is_completed())
.count()
}
pub(super) fn ensure_block(&self, block_index: usize) -> Arc<TaskStateBlock> {
if let BlockLookup::Live(block) = self
.blocks
.read()
.expect("task registry block directory is never poisoned")
.lookup(block_index)
{
return Arc::clone(block);
}
let ensured = self
.blocks
.write()
.expect("task registry block directory is never poisoned")
.ensure(block_index);
let (block, created) =
ensured.expect("invariant: a block holding an issued, unregistered id never retires");
if created {
self.sweep_step();
}
block
}
pub(super) fn with_state<R>(&self, task_id: u64, f: impl FnOnce(&TaskState) -> R) -> Option<R> {
match self.observe(task_id, f) {
Observation::Registered(value) => Some(value),
Observation::Retired | Observation::Unregistered => None,
}
}
fn observe<R>(&self, task_id: u64, f: impl FnOnce(&TaskState) -> R) -> Observation<R> {
let (block_index, slot_index) = task_location(task_id);
let block = {
let blocks = self
.blocks
.read()
.expect("task registry block directory is never poisoned");
match blocks.lookup(block_index) {
BlockLookup::Live(block) => Arc::clone(block),
BlockLookup::Retired => return Observation::Retired,
BlockLookup::Absent => return Observation::Unregistered,
}
};
block
.get(slot_index)
.map_or(Observation::Unregistered, |state| {
Observation::Registered(f(state))
})
}
pub(crate) fn request_cancel(&self, task_id: u64) -> Option<CancelOutcome> {
match self.observe(task_id, |state| {
if state.is_completed() {
CancelOutcome::AlreadyCompleted
} else {
state.request_cancel();
CancelOutcome::Requested
}
}) {
Observation::Retired => Some(CancelOutcome::AlreadyCompleted),
Observation::Unregistered => None,
Observation::Registered(outcome) => Some(outcome),
}
}
pub fn register_waker(&self, task_id: u64, waker: &std::task::Waker) -> bool {
self.with_state(task_id, |state| state.register_waker(waker))
.is_some()
}
}
impl Default for TaskRegistry {
fn default() -> Self {
Self::new()
}
}