use crate::plugin::PluginIdentity;
use std::{
fmt::{Debug, Display, Formatter},
num::{NonZeroU64, NonZeroUsize},
};
use super::PluginWorkspaceIoCommitError;
const DEFAULT_WORKSPACE_IO_REQUESTS_PER_UPDATE: usize = 32;
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum PluginWorkspaceIoBudgetField {
MaxRequests,
MaxReadBytes,
MaxWriteBytes,
}
impl PluginWorkspaceIoBudgetField {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::MaxRequests => "max_requests",
Self::MaxReadBytes => "max_read_bytes",
Self::MaxWriteBytes => "max_write_bytes",
}
}
}
impl Display for PluginWorkspaceIoBudgetField {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Debug for PluginWorkspaceIoBudgetField {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct PluginWorkspaceReadByteLimit(NonZeroU64);
impl PluginWorkspaceReadByteLimit {
pub const fn try_new(max_bytes: u64) -> Result<Self, PluginWorkspaceIoCommitError> {
let Some(max_bytes) = NonZeroU64::new(max_bytes) else {
return Err(PluginWorkspaceIoCommitError::ZeroLimit {
field: PluginWorkspaceIoBudgetField::MaxReadBytes,
});
};
Ok(Self(max_bytes))
}
#[must_use]
pub const fn get(self) -> u64 {
self.0.get()
}
}
impl Debug for PluginWorkspaceReadByteLimit {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("PluginWorkspaceReadByteLimit")
.field(&self.get())
.finish()
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct PluginWorkspaceWriteByteLimit(NonZeroU64);
impl PluginWorkspaceWriteByteLimit {
pub const fn try_new(max_bytes: u64) -> Result<Self, PluginWorkspaceIoCommitError> {
let Some(max_bytes) = NonZeroU64::new(max_bytes) else {
return Err(PluginWorkspaceIoCommitError::ZeroLimit {
field: PluginWorkspaceIoBudgetField::MaxWriteBytes,
});
};
Ok(Self(max_bytes))
}
#[must_use]
pub const fn get(self) -> u64 {
self.0.get()
}
}
impl Debug for PluginWorkspaceWriteByteLimit {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("PluginWorkspaceWriteByteLimit")
.field(&self.get())
.finish()
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct PluginWorkspaceIoBudget {
requests: NonZeroUsize,
read_bytes: PluginWorkspaceReadByteLimit,
write_bytes: PluginWorkspaceWriteByteLimit,
}
impl PluginWorkspaceIoBudget {
pub const fn try_new(
max_requests: usize,
max_read_bytes: u64,
max_write_bytes: u64,
) -> Result<Self, PluginWorkspaceIoCommitError> {
let Some(requests) = NonZeroUsize::new(max_requests) else {
return Err(PluginWorkspaceIoCommitError::ZeroLimit {
field: PluginWorkspaceIoBudgetField::MaxRequests,
});
};
let Some(read_bytes) = NonZeroU64::new(max_read_bytes) else {
return Err(PluginWorkspaceIoCommitError::ZeroLimit {
field: PluginWorkspaceIoBudgetField::MaxReadBytes,
});
};
let Some(write_bytes) = NonZeroU64::new(max_write_bytes) else {
return Err(PluginWorkspaceIoCommitError::ZeroLimit {
field: PluginWorkspaceIoBudgetField::MaxWriteBytes,
});
};
Ok(Self {
requests,
read_bytes: PluginWorkspaceReadByteLimit(read_bytes),
write_bytes: PluginWorkspaceWriteByteLimit(write_bytes),
})
}
pub const fn from_max_message_bytes(
max_message_bytes: u64,
) -> Result<Self, PluginWorkspaceIoCommitError> {
Self::try_new(
DEFAULT_WORKSPACE_IO_REQUESTS_PER_UPDATE,
max_message_bytes,
max_message_bytes,
)
}
#[must_use]
pub(in crate::plugin) const fn from_max_message_byte_limit(
max_message_bytes: NonZeroU64,
) -> Self {
let requests = match NonZeroUsize::new(DEFAULT_WORKSPACE_IO_REQUESTS_PER_UPDATE) {
Some(requests) => requests,
None => NonZeroUsize::MIN,
};
Self {
requests,
read_bytes: PluginWorkspaceReadByteLimit(max_message_bytes),
write_bytes: PluginWorkspaceWriteByteLimit(max_message_bytes),
}
}
#[must_use]
pub const fn max_requests(self) -> usize {
self.requests.get()
}
#[must_use]
pub(in crate::plugin::host) const fn max_requests_limit(self) -> NonZeroUsize {
self.requests
}
#[must_use]
pub const fn max_read_bytes(self) -> u64 {
self.read_bytes.get()
}
#[must_use]
pub(in crate::plugin::host) const fn max_read_bytes_limit(
self,
) -> PluginWorkspaceReadByteLimit {
self.read_bytes
}
#[must_use]
pub const fn max_write_bytes(self) -> u64 {
self.write_bytes.get()
}
#[must_use]
pub(in crate::plugin::host) const fn max_write_bytes_limit(
self,
) -> PluginWorkspaceWriteByteLimit {
self.write_bytes
}
}
impl Debug for PluginWorkspaceIoBudget {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PluginWorkspaceIoBudget")
.field("requests", &self.max_requests())
.field("read_bytes", &self.max_read_bytes())
.field("write_bytes", &self.max_write_bytes())
.finish()
}
}
#[derive(Eq, PartialEq)]
pub(in crate::plugin::host) struct PluginWorkspaceIoRequestLedger {
identity: PluginIdentity,
budget: PluginWorkspaceIoBudget,
non_deferred_requests: usize,
}
pub(in crate::plugin::host) struct PluginWorkspaceIoRequestLedgerParts {
pub identity: PluginIdentity,
pub budget: PluginWorkspaceIoBudget,
}
impl PluginWorkspaceIoRequestLedger {
pub(in crate::plugin::host) const fn new(
identity: PluginIdentity,
budget: PluginWorkspaceIoBudget,
) -> Self {
Self {
identity,
budget,
non_deferred_requests: 0,
}
}
#[must_use]
pub(in crate::plugin::host) const fn identity_proof(&self) -> &PluginIdentity {
&self.identity
}
#[must_use]
pub(in crate::plugin::host) const fn non_deferred_request_count(&self) -> usize {
self.non_deferred_requests
}
#[must_use]
pub(in crate::plugin::host) const fn accepted_request_count(
&self,
deferred_requests: usize,
) -> usize {
match deferred_requests.checked_add(self.non_deferred_requests) {
Some(count) => count,
None => usize::MAX,
}
}
#[must_use]
pub(in crate::plugin::host) const fn max_read_bytes(&self) -> u64 {
self.budget.max_read_bytes()
}
#[must_use]
pub(in crate::plugin::host) const fn max_read_bytes_limit(
&self,
) -> PluginWorkspaceReadByteLimit {
self.budget.max_read_bytes_limit()
}
#[must_use]
pub(in crate::plugin::host) const fn max_write_bytes_limit(
&self,
) -> PluginWorkspaceWriteByteLimit {
self.budget.max_write_bytes_limit()
}
pub(in crate::plugin::host) fn ensure_deferred_request_budget(
&self,
deferred_requests: usize,
) -> Result<(), PluginWorkspaceIoCommitError> {
self.ensure_request_budget(deferred_requests)
}
pub(in crate::plugin::host) fn prepare_non_deferred_read(
&mut self,
deferred_requests: usize,
) -> Result<PluginWorkspaceNonDeferredReadPermit<'_>, PluginWorkspaceIoCommitError> {
self.ensure_request_budget(deferred_requests)?;
Ok(PluginWorkspaceNonDeferredReadPermit { ledger: self })
}
#[must_use]
pub(in crate::plugin::host) fn into_parts(self) -> PluginWorkspaceIoRequestLedgerParts {
PluginWorkspaceIoRequestLedgerParts {
identity: self.identity,
budget: self.budget,
}
}
fn ensure_request_budget(
&self,
deferred_requests: usize,
) -> Result<(), PluginWorkspaceIoCommitError> {
if self.accepted_request_count(deferred_requests) >= self.budget.max_requests() {
return Err(PluginWorkspaceIoCommitError::TooManyRequests {
identity: self.identity.clone(),
limit: self.budget.max_requests_limit(),
});
}
Ok(())
}
}
impl Debug for PluginWorkspaceIoRequestLedger {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PluginWorkspaceIoRequestLedger")
.field("identity", &self.identity)
.field("budget", &self.budget)
.field("non_deferred_requests", &self.non_deferred_requests)
.finish()
}
}
#[must_use = "dropping the permit leaves the workspace read request uncharged"]
pub(in crate::plugin::host) struct PluginWorkspaceNonDeferredReadPermit<'ledger> {
ledger: &'ledger mut PluginWorkspaceIoRequestLedger,
}
impl PluginWorkspaceNonDeferredReadPermit<'_> {
#[must_use]
pub(in crate::plugin::host) const fn max_read_bytes_limit(
&self,
) -> PluginWorkspaceReadByteLimit {
self.ledger.max_read_bytes_limit()
}
pub(in crate::plugin::host) const fn commit(self) -> PluginWorkspaceReadByteLimit {
let max_read_bytes = self.max_read_bytes_limit();
self.ledger.non_deferred_requests += 1;
max_read_bytes
}
}