use super::{
PluginWorkspaceIoDiscardReport, PluginWorkspaceIoReport, SealedPluginWorkspaceIoBatch,
};
use crate::{
fs_utils::FilesystemConfig,
plugin::{PluginIdentity, PluginOperationalEvent, PluginOperationalQueue},
};
use std::{
collections::VecDeque,
fmt::{Debug, Display, Formatter},
num::NonZeroUsize,
};
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum PluginWorkspaceIoWorkerQueueLimitField {
MaxBatches,
}
impl PluginWorkspaceIoWorkerQueueLimitField {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::MaxBatches => "max_batches",
}
}
}
impl Display for PluginWorkspaceIoWorkerQueueLimitField {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Debug for PluginWorkspaceIoWorkerQueueLimitField {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PluginWorkspaceIoWorkerQueueLimit {
max_batches: NonZeroUsize,
}
impl PluginWorkspaceIoWorkerQueueLimit {
pub const fn try_new(max_batches: usize) -> Result<Self, PluginWorkspaceIoWorkerQueueError> {
match NonZeroUsize::new(max_batches) {
Some(max_batches) => Ok(Self { max_batches }),
None => Err(PluginWorkspaceIoWorkerQueueError::ZeroLimit {
field: PluginWorkspaceIoWorkerQueueLimitField::MaxBatches,
}),
}
}
#[must_use]
pub const fn max_batches(self) -> usize {
self.max_batches.get()
}
#[must_use]
const fn max_batches_limit(self) -> NonZeroUsize {
self.max_batches
}
}
impl Default for PluginWorkspaceIoWorkerQueueLimit {
fn default() -> Self {
Self::try_new(1024).expect("default workspace queue limit should be non-zero")
}
}
pub struct PluginWorkspaceIoWorkerQueue {
limit: PluginWorkspaceIoWorkerQueueLimit,
batches: VecDeque<SealedPluginWorkspaceIoBatch>,
}
impl PluginWorkspaceIoWorkerQueue {
#[must_use]
pub const fn with_limit(limit: PluginWorkspaceIoWorkerQueueLimit) -> Self {
Self::from_validated_limit(limit)
}
#[must_use]
pub(in crate::plugin) const fn from_validated_limit(
limit: PluginWorkspaceIoWorkerQueueLimit,
) -> Self {
Self {
limit,
batches: VecDeque::new(),
}
}
pub fn push(
&mut self,
batch: SealedPluginWorkspaceIoBatch,
) -> Result<(), PluginWorkspaceIoWorkerEnqueueError> {
self.admit(batch)?.publish();
Ok(())
}
pub(crate) fn admit(
&mut self,
batch: SealedPluginWorkspaceIoBatch,
) -> Result<PluginWorkspaceIoWorkerQueueAdmission<'_>, PluginWorkspaceIoWorkerEnqueueError>
{
if batch.is_empty() {
return Ok(PluginWorkspaceIoWorkerQueueAdmission::new(
batch,
&mut self.batches,
));
}
if self.batches.len() >= self.limit.max_batches() {
let source = PluginWorkspaceIoWorkerQueueError::TooManyBatches {
identity: batch.identity_proof().clone(),
limit: self.limit.max_batches_limit(),
};
return Err(PluginWorkspaceIoWorkerEnqueueError::new(source, batch));
}
Ok(PluginWorkspaceIoWorkerQueueAdmission::new(
batch,
&mut self.batches,
))
}
#[must_use]
pub fn cancel_identity(
&mut self,
identity: &PluginIdentity,
) -> PluginWorkspaceIoCancellationReport {
let mut retained = VecDeque::with_capacity(self.batches.len());
let mut report = PluginWorkspaceIoCancellationReport::new(identity.clone());
while let Some(batch) = self.batches.pop_front() {
if batch.identity_proof() == identity {
report.record_batch(batch.request_count());
} else {
retained.push_back(batch);
}
}
self.batches = retained;
report
}
#[must_use]
pub fn cancel_all(&mut self) -> Vec<PluginWorkspaceIoCancellationReport> {
let mut reports = PluginWorkspaceIoCancellationReports::new();
while let Some(batch) = self.batches.pop_front() {
reports.record_batch(batch.identity_proof(), batch.request_count());
}
reports.into_vec()
}
pub fn execute_next(
&mut self,
filesystem: &FilesystemConfig,
) -> Option<PluginWorkspaceIoReport> {
self.batches
.pop_front()
.map(|batch| batch.execute_synchronous(filesystem))
}
pub fn execute_all(&mut self, filesystem: &FilesystemConfig) -> Vec<PluginWorkspaceIoReport> {
let mut reports = Vec::with_capacity(self.batches.len());
while let Some(report) = self.execute_next(filesystem) {
reports.push(report);
}
reports
}
#[must_use]
pub const fn limit(&self) -> PluginWorkspaceIoWorkerQueueLimit {
self.limit
}
#[must_use]
pub fn len(&self) -> usize {
self.batches.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.batches.is_empty()
}
}
impl Default for PluginWorkspaceIoWorkerQueue {
fn default() -> Self {
Self::from_validated_limit(PluginWorkspaceIoWorkerQueueLimit::default())
}
}
impl Debug for PluginWorkspaceIoWorkerQueue {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PluginWorkspaceIoWorkerQueue")
.field("limit", &self.limit)
.field("batch_count", &self.batches.len())
.field(
"batch_shapes",
&self
.batches
.iter()
.map(|batch| PluginWorkspaceIoWorkerBatchShape {
identity: batch.identity_proof().clone(),
request_count: batch.request_count(),
})
.collect::<Vec<_>>(),
)
.finish()
}
}
#[must_use]
pub struct PluginWorkspaceIoWorkerQueueAdmission<'queue> {
batch: SealedPluginWorkspaceIoBatch,
batches: &'queue mut VecDeque<SealedPluginWorkspaceIoBatch>,
}
impl<'queue> PluginWorkspaceIoWorkerQueueAdmission<'queue> {
const fn new(
batch: SealedPluginWorkspaceIoBatch,
batches: &'queue mut VecDeque<SealedPluginWorkspaceIoBatch>,
) -> Self {
Self { batch, batches }
}
pub(crate) fn publish(self) {
let Self { batch, batches } = self;
if !batch.is_empty() {
batches.push_back(batch);
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct PluginWorkspaceIoWorkerBatchShape {
identity: PluginIdentity,
request_count: usize,
}
struct PluginWorkspaceIoCancellationReports {
reports: Vec<PluginWorkspaceIoCancellationReport>,
}
impl PluginWorkspaceIoCancellationReports {
const fn new() -> Self {
Self {
reports: Vec::new(),
}
}
fn record_batch(&mut self, identity: &PluginIdentity, request_count: usize) {
self.report_for(identity).record_batch(request_count);
}
fn into_vec(self) -> Vec<PluginWorkspaceIoCancellationReport> {
self.reports
}
fn report_for(
&mut self,
identity: &PluginIdentity,
) -> &mut PluginWorkspaceIoCancellationReport {
if let Some(index) = self
.reports
.iter()
.position(|report| report.identity_proof() == identity)
{
return &mut self.reports[index];
}
let index = self.reports.len();
self.reports
.push(PluginWorkspaceIoCancellationReport::new(identity.clone()));
&mut self.reports[index]
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PluginWorkspaceIoCancellationReport {
identity: PluginIdentity,
canceled_batches: usize,
canceled_requests: usize,
}
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
#[error("{source}")]
pub struct PluginWorkspaceIoWorkerEnqueueError {
source: PluginWorkspaceIoWorkerQueueError,
batch: SealedPluginWorkspaceIoBatch,
}
pub struct PluginWorkspaceIoWorkerEnqueueErrorParts {
pub queue_error: PluginWorkspaceIoWorkerQueueError,
pub batch: SealedPluginWorkspaceIoBatch,
}
impl PluginWorkspaceIoWorkerEnqueueError {
const fn new(
source: PluginWorkspaceIoWorkerQueueError,
batch: SealedPluginWorkspaceIoBatch,
) -> Self {
Self { source, batch }
}
#[must_use]
pub const fn queue_error(&self) -> &PluginWorkspaceIoWorkerQueueError {
&self.source
}
#[must_use]
pub const fn batch(&self) -> &SealedPluginWorkspaceIoBatch {
&self.batch
}
#[must_use]
pub fn into_parts(self) -> PluginWorkspaceIoWorkerEnqueueErrorParts {
PluginWorkspaceIoWorkerEnqueueErrorParts {
queue_error: self.source,
batch: self.batch,
}
}
#[must_use]
pub fn into_batch(self) -> SealedPluginWorkspaceIoBatch {
self.batch
}
#[must_use]
pub fn discard_batch(self) -> PluginWorkspaceIoDiscardReport {
self.batch.discard()
}
}
impl PluginWorkspaceIoCancellationReport {
const fn new(identity: PluginIdentity) -> Self {
Self {
identity,
canceled_batches: 0,
canceled_requests: 0,
}
}
const fn record_batch(&mut self, request_count: usize) {
self.canceled_batches += 1;
self.canceled_requests += request_count;
}
#[must_use]
pub fn identity(&self) -> &str {
self.identity_proof().as_str()
}
#[must_use]
pub const fn identity_proof(&self) -> &PluginIdentity {
&self.identity
}
#[must_use]
pub const fn canceled_batches(&self) -> usize {
self.canceled_batches
}
#[must_use]
pub const fn canceled_requests(&self) -> usize {
self.canceled_requests
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum PluginWorkspaceIoWorkerQueueErrorKind {
ZeroLimit,
TooManyBatches,
}
impl PluginWorkspaceIoWorkerQueueErrorKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ZeroLimit => "zero-limit",
Self::TooManyBatches => "too-many-batches",
}
}
}
impl Display for PluginWorkspaceIoWorkerQueueErrorKind {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Debug for PluginWorkspaceIoWorkerQueueErrorKind {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum PluginWorkspaceIoWorkerQueueError {
ZeroLimit {
field: PluginWorkspaceIoWorkerQueueLimitField,
},
TooManyBatches {
identity: PluginIdentity,
limit: NonZeroUsize,
},
}
impl Display for PluginWorkspaceIoWorkerQueueError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ZeroLimit { .. } => {
formatter.write_str("plugin workspace I/O queue limit must be non-zero")
}
Self::TooManyBatches { identity, limit } => {
let identity = identity.as_str();
let limit = limit.get();
write!(
formatter,
"plugin {identity:?} exceeded workspace I/O queue limit of {limit}"
)
}
}
}
}
impl PluginWorkspaceIoWorkerQueueError {
#[must_use]
pub const fn kind(&self) -> PluginWorkspaceIoWorkerQueueErrorKind {
match self {
Self::ZeroLimit { .. } => PluginWorkspaceIoWorkerQueueErrorKind::ZeroLimit,
Self::TooManyBatches { .. } => PluginWorkspaceIoWorkerQueueErrorKind::TooManyBatches,
}
}
#[must_use]
pub fn operational_event(&self) -> Option<PluginOperationalEvent> {
match self {
Self::TooManyBatches { identity, limit } => {
Some(PluginOperationalEvent::queue_saturated_for(
identity,
PluginOperationalQueue::WorkspaceIo,
*limit,
))
}
Self::ZeroLimit { .. } => None,
}
}
}