use std::backtrace::Backtrace;
use std::error::Error;
use std::fmt;
use std::panic::Location;
use std::sync::Arc;
use rocketmq_error::CanonicalCondition;
use rocketmq_error::DiagnosticView;
use rocketmq_error::Error as CanonicalError;
use rocketmq_error::ErrorCode;
use rocketmq_error::ErrorContext;
use rocketmq_error::ErrorDescriptor;
use rocketmq_error::PublicErrorView;
use rocketmq_error::RecoveryHint;
use rocketmq_error::SharedError;
use crate::resource_budget::BudgetDimension;
pub type RuntimeResult<T> = Result<T, RuntimeError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RuntimeOperation {
BuildTokioRuntime,
LoadConfigFile,
DeserializeConfigFile,
DetectProcessMemoryLimit,
ResolveManagedMemoryBudget,
ReadFile,
ReadFileBackup,
PersistMetadata,
MetadataCreateParent,
MetadataCreateTemporary,
MetadataWriteTemporary,
MetadataSyncTemporary,
MetadataReplaceTarget,
MetadataSyncParent,
MetadataRemoveTemporary,
WaitForDurableMetadata,
AdmitMetadataSnapshot,
AdmitMetadataOperation,
AdmitMetadataBytes,
MetadataWorkerStopped,
MetadataIoClosed,
RegisterSigtermHandler,
RegisterSigintHandler,
WaitForSignal,
CreateTaskGroupChild,
SpawnTaskGroupTask,
SpawnServiceTask,
SpawnSchedulerTask,
RuntimeContext,
StartServiceLifecycle,
ServiceLifecycleTaskGroup,
BindServiceHealthProbe,
InspectServiceHealthProbe,
MarkServiceReady,
SuspendServiceReadiness,
RestoreServiceReadiness,
ShutdownRuntimeBlocking,
BlockingQueueAdmission,
RunBlockingTask,
BlockingTaskDeadline,
BlockingTask,
BlockingExecutorKind,
ShutdownBlockingExecutor,
ClearCompletedSchedules,
RegisterScheduledTask,
SpawnOperation,
OperationOwner,
MetadataIo,
ServiceLifecycleEnvironment,
ServiceLifecycleProbeAddress,
ServiceLifecycleDuration,
ServiceLifecycleDurationRange,
ServiceLifecycleDrainMethods,
MetadataResourceTarget,
External(ExternalOperation),
}
impl RuntimeOperation {
#[must_use]
pub const fn external(label: &'static str) -> Self {
Self::External(ExternalOperation::new(label))
}
const fn diagnostic_label(self) -> &'static str {
match self {
Self::BuildTokioRuntime => "build-tokio-runtime",
Self::LoadConfigFile => "load-config-file",
Self::DeserializeConfigFile => "deserialize-config-file",
Self::DetectProcessMemoryLimit => "detect-process-memory-limit",
Self::ResolveManagedMemoryBudget => "resolve-managed-memory-budget",
Self::ReadFile => "read-file",
Self::ReadFileBackup => "read-file-backup",
Self::PersistMetadata => "metadata-persistence",
Self::MetadataCreateParent => "metadata-create-parent",
Self::MetadataCreateTemporary => "metadata-create-temporary",
Self::MetadataWriteTemporary => "metadata-write-temporary",
Self::MetadataSyncTemporary => "metadata-sync-temporary",
Self::MetadataReplaceTarget => "metadata-replace-target",
Self::MetadataSyncParent => "metadata-sync-parent",
Self::MetadataRemoveTemporary => "metadata-remove-temporary",
Self::WaitForDurableMetadata => "wait-for-durable-metadata",
Self::AdmitMetadataSnapshot => "admit-metadata-snapshot",
Self::AdmitMetadataOperation => "admit-metadata-operation",
Self::AdmitMetadataBytes => "admit-metadata-bytes",
Self::MetadataWorkerStopped => "metadata-worker-stopped",
Self::MetadataIoClosed => "metadata-io-closed",
Self::RegisterSigtermHandler => "register-sigterm-handler",
Self::RegisterSigintHandler => "register-sigint-handler",
Self::WaitForSignal => "wait-for-signal",
Self::CreateTaskGroupChild => "create-task-group-child",
Self::SpawnTaskGroupTask => "spawn-task-group-task",
Self::SpawnServiceTask => "spawn-service-task",
Self::SpawnSchedulerTask => "spawn-scheduler-task",
Self::RuntimeContext => "runtime-context",
Self::StartServiceLifecycle => "start-service-lifecycle",
Self::ServiceLifecycleTaskGroup => "service-lifecycle-task-group",
Self::BindServiceHealthProbe => "bind-service-health-probe",
Self::InspectServiceHealthProbe => "inspect-service-health-probe",
Self::MarkServiceReady => "mark-service-ready",
Self::SuspendServiceReadiness => "suspend-service-readiness",
Self::RestoreServiceReadiness => "restore-service-readiness",
Self::ShutdownRuntimeBlocking => "shutdown-runtime-blocking",
Self::BlockingQueueAdmission => "blocking-queue-admission",
Self::RunBlockingTask => "run-blocking-task",
Self::BlockingTaskDeadline => "blocking-task-deadline",
Self::BlockingTask => "blocking-task",
Self::BlockingExecutorKind => "blocking-executor-kind",
Self::ShutdownBlockingExecutor => "shutdown-blocking-executor",
Self::ClearCompletedSchedules => "clear-completed-schedules",
Self::RegisterScheduledTask => "register-scheduled-task",
Self::SpawnOperation => "spawn-operation",
Self::OperationOwner => "operation-owner",
Self::MetadataIo => "metadata-io",
Self::ServiceLifecycleEnvironment => "service-lifecycle-environment",
Self::ServiceLifecycleProbeAddress => "service-lifecycle-probe-address",
Self::ServiceLifecycleDuration => "service-lifecycle-duration",
Self::ServiceLifecycleDurationRange => "service-lifecycle-duration-range",
Self::ServiceLifecycleDrainMethods => "service-lifecycle-drain-methods",
Self::MetadataResourceTarget => "metadata-resource-target",
Self::External(operation) => operation.label(),
}
}
}
impl fmt::Display for RuntimeOperation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.diagnostic_label())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExternalOperation(&'static str);
impl ExternalOperation {
pub const MAX_LABEL_BYTES: usize = 64;
#[must_use]
pub const fn new(label: &'static str) -> Self {
assert!(
is_valid_external_label(label),
"external operation labels are 1-64 bytes of lowercase ASCII letters, digits, and inner hyphens"
);
Self(label)
}
#[must_use]
pub const fn label(self) -> &'static str {
self.0
}
}
const fn is_valid_external_label(label: &str) -> bool {
let bytes = label.as_bytes();
if bytes.is_empty() || bytes.len() > ExternalOperation::MAX_LABEL_BYTES {
return false;
}
if bytes[0] == b'-' || bytes[bytes.len() - 1] == b'-' {
return false;
}
let mut index = 0;
while index < bytes.len() {
let byte = bytes[index];
if !(byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-') {
return false;
}
index += 1;
}
true
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RuntimeContractPolicy {
MaxBlockingThreadsWithinSupportedRange,
WorkerThreadsPositive,
MaxBlockingThreadsPositive,
ThreadNameNotBlank,
ThreadStackSizePositive,
BlockingMaxConcurrencyPositive,
BlockingMaxQueueDepthPositive,
BlockingTimeoutRepresentable,
BlockingGlobalCapacityCoversLanes,
ServiceContextScopeNotBlank,
ConfiguredMemoryLimitPositive,
MemoryFractionPositiveAndBounded,
ManagedMemoryRatioPositiveAndBounded,
ManagedMemoryHeadroomBelowEffectiveLimit,
ManagedMemoryBudgetPositive,
CriticalFailureSubscriptionCapacityPositive,
MetadataMaxPendingOperationsPositive,
MetadataMaxPendingBytesPositive,
MetadataBlockingTaskTimeoutPositive,
}
impl RuntimeContractPolicy {
const fn diagnostic_label(self) -> &'static str {
match self {
Self::MaxBlockingThreadsWithinSupportedRange => "max-blocking-threads-within-supported-range",
Self::WorkerThreadsPositive => "worker-threads-positive",
Self::MaxBlockingThreadsPositive => "max-blocking-threads-positive",
Self::ThreadNameNotBlank => "thread-name-not-blank",
Self::ThreadStackSizePositive => "thread-stack-size-positive",
Self::BlockingMaxConcurrencyPositive => "blocking-max-concurrency-positive",
Self::BlockingMaxQueueDepthPositive => "blocking-max-queue-depth-positive",
Self::BlockingTimeoutRepresentable => "blocking-timeout-representable",
Self::BlockingGlobalCapacityCoversLanes => "blocking-global-capacity-covers-lanes",
Self::ServiceContextScopeNotBlank => "service-context-scope-not-blank",
Self::ConfiguredMemoryLimitPositive => "configured-limit-must-be-positive",
Self::MemoryFractionPositiveAndBounded => "fraction-must-be-positive-and-bounded",
Self::ManagedMemoryRatioPositiveAndBounded => "managed-memory-ratio-positive-and-bounded",
Self::ManagedMemoryHeadroomBelowEffectiveLimit => "managed-memory-headroom-below-effective-limit",
Self::ManagedMemoryBudgetPositive => "managed-memory-budget-positive",
Self::CriticalFailureSubscriptionCapacityPositive => "critical-failure-subscription-capacity-positive",
Self::MetadataMaxPendingOperationsPositive => "max-pending-operations-positive",
Self::MetadataMaxPendingBytesPositive => "max-pending-bytes-positive",
Self::MetadataBlockingTaskTimeoutPositive => "blocking-task-timeout-positive",
}
}
}
impl fmt::Display for RuntimeContractPolicy {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.diagnostic_label())
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RuntimeContractViolation {
#[error("runtime configuration violates {policy}")]
InvalidConfiguration {
policy: RuntimeContractPolicy,
},
#[error("resource budget name must not be blank")]
EmptyBudgetName,
#[error("resource budget name must not contain '/'")]
InvalidBudgetName,
#[error("resource budget capacity is zero")]
ZeroBudgetCapacity {
dimension: BudgetDimension,
},
#[error("resource budget rate must be positive")]
ZeroBudgetRate,
#[error("resource budget maximum age must be positive")]
ZeroBudgetMaxAge,
#[error("resource budget reserve exceeds capacity")]
ReserveExceedsBudgetCapacity {
dimension: BudgetDimension,
},
#[error("resource budget reserve requires capacity")]
ReserveWithoutBudgetCapacity {
dimension: BudgetDimension,
},
#[error("resource budget child limit exceeds parent")]
ChildBudgetExceedsParent {
dimension: BudgetDimension,
},
#[error("resource budget child maximum age exceeds parent")]
ChildBudgetMaxAgeExceedsParent,
#[error("resource permit target belongs to a different budget tree")]
PermitTargetInDifferentTree,
#[error("process memory limit violates {policy}")]
InvalidMemoryLimit {
policy: RuntimeContractPolicy,
},
#[error("schedule definition violates {policy}")]
InvalidSchedule {
policy: RuntimeContractPolicy,
},
#[error("metadata I/O configuration violates {policy}")]
InvalidMetadataConfiguration {
policy: RuntimeContractPolicy,
},
}
impl RuntimeContractViolation {
#[must_use]
pub const fn condition(&self) -> CanonicalCondition {
CanonicalCondition::InvalidArgument
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RuntimeErrorKind {
Closed,
Poisoned,
TimedOut,
CapacityExhausted,
Unsupported,
ContextUnavailable,
Configuration,
Io,
Internal,
}
#[derive(Clone)]
pub struct RuntimeError {
error: SharedError,
kind: RuntimeErrorKind,
operation: RuntimeOperation,
component: &'static str,
}
impl RuntimeError {
#[must_use]
#[track_caller]
fn new(
descriptor: &'static ErrorDescriptor,
kind: RuntimeErrorKind,
operation: RuntimeOperation,
component: &'static str,
) -> Self {
let error = CanonicalError::new(descriptor).with_context(runtime_context(operation, false));
Self {
error: Arc::new(error),
kind,
operation,
component,
}
}
#[must_use]
#[track_caller]
fn caused_by(
descriptor: &'static ErrorDescriptor,
kind: RuntimeErrorKind,
operation: RuntimeOperation,
component: &'static str,
source: impl Error + Send + Sync + 'static,
) -> Self {
let error = CanonicalError::caused_by(descriptor, source).with_context(runtime_context(operation, true));
Self {
error: Arc::new(error),
kind,
operation,
component,
}
}
#[must_use]
#[track_caller]
pub(crate) fn within(operation: RuntimeOperation, source: RuntimeError) -> Self {
Self::caused_by(source.descriptor(), source.kind, operation, source.component, source)
}
#[must_use]
pub fn descriptor(&self) -> &'static ErrorDescriptor {
self.error.descriptor()
}
#[must_use]
pub fn code(&self) -> ErrorCode {
self.error.code()
}
#[must_use]
pub fn condition(&self) -> CanonicalCondition {
self.error.condition()
}
#[must_use]
pub fn recovery_hint(&self) -> RecoveryHint {
self.error.recovery_hint()
}
#[must_use]
pub const fn kind(&self) -> RuntimeErrorKind {
self.kind
}
#[must_use]
pub const fn operation(&self) -> RuntimeOperation {
self.operation
}
#[must_use]
pub fn component(&self) -> &str {
self.component
}
#[must_use]
pub fn context(&self) -> &ErrorContext {
self.error.context()
}
#[must_use]
pub fn location(&self) -> &'static Location<'static> {
self.error.location()
}
#[must_use]
pub fn backtrace(&self) -> Option<&Backtrace> {
self.error.backtrace()
}
pub fn public_view(&self) -> Result<PublicErrorView<'_>, rocketmq_error::ViewContextViolation> {
self.error.public_view()
}
pub fn diagnostic_view(&self) -> Result<DiagnosticView<'_>, rocketmq_error::ViewContextViolation> {
self.error.diagnostic_view()
}
#[must_use]
#[track_caller]
pub fn configuration(operation: RuntimeOperation) -> Self {
Self::new(
&rocketmq_error::RUNTIME_CONFIGURATION_FAILED,
RuntimeErrorKind::Configuration,
operation,
"runtime",
)
}
#[must_use]
#[track_caller]
pub fn configuration_failure(operation: RuntimeOperation, source: impl Error + Send + Sync + 'static) -> Self {
Self::caused_by(
&rocketmq_error::RUNTIME_CONFIGURATION_FAILED,
RuntimeErrorKind::Configuration,
operation,
"runtime",
source,
)
}
#[must_use]
#[track_caller]
pub fn build(operation: RuntimeOperation, source: std::io::Error) -> Self {
Self::caused_by(
&rocketmq_error::RUNTIME_BUILD_FAILED,
RuntimeErrorKind::Io,
operation,
"runtime",
source,
)
}
#[must_use]
#[track_caller]
pub fn io(operation: RuntimeOperation, source: std::io::Error) -> Self {
Self::caused_by(
&rocketmq_error::RUNTIME_IO_FAILED,
RuntimeErrorKind::Io,
operation,
"runtime",
source,
)
}
#[must_use]
#[track_caller]
pub fn context_unavailable(operation: RuntimeOperation) -> Self {
Self::new(
&rocketmq_error::RUNTIME_CONTEXT_UNAVAILABLE,
RuntimeErrorKind::ContextUnavailable,
operation,
"runtime",
)
}
#[must_use]
#[track_caller]
pub fn closed(operation: RuntimeOperation) -> Self {
Self::new(
&rocketmq_error::RUNTIME_CONTEXT_UNAVAILABLE,
RuntimeErrorKind::Closed,
operation,
"runtime",
)
}
#[must_use]
#[track_caller]
pub(crate) fn poisoned(operation: RuntimeOperation) -> Self {
Self::new(
&rocketmq_error::RUNTIME_CONTEXT_UNAVAILABLE,
RuntimeErrorKind::Poisoned,
operation,
"runtime",
)
}
#[must_use]
#[track_caller]
pub fn capacity(operation: RuntimeOperation) -> Self {
Self::new(
&rocketmq_error::RUNTIME_CAPACITY_EXHAUSTED,
RuntimeErrorKind::CapacityExhausted,
operation,
"runtime",
)
}
#[must_use]
#[track_caller]
pub fn timed_out(operation: RuntimeOperation) -> Self {
Self::new(
&rocketmq_error::RUNTIME_OPERATION_TIMED_OUT,
RuntimeErrorKind::TimedOut,
operation,
"runtime",
)
}
#[must_use]
#[track_caller]
pub fn unsupported(operation: RuntimeOperation) -> Self {
Self::new(
&rocketmq_error::RUNTIME_OPERATION_UNSUPPORTED,
RuntimeErrorKind::Unsupported,
operation,
"runtime",
)
}
#[must_use]
#[track_caller]
pub fn join(operation: RuntimeOperation, source: tokio::task::JoinError) -> Self {
Self::caused_by(
&rocketmq_error::RUNTIME_TASK_JOIN_FAILED,
RuntimeErrorKind::Internal,
operation,
"task",
source,
)
}
#[must_use]
#[track_caller]
pub fn internal(operation: RuntimeOperation, source: impl Error + Send + Sync + 'static) -> Self {
Self::caused_by(
&rocketmq_error::RUNTIME_INTERNAL_FAILURE,
RuntimeErrorKind::Internal,
operation,
"runtime",
source,
)
}
#[must_use]
#[track_caller]
pub fn internal_failure(operation: RuntimeOperation) -> Self {
Self::new(
&rocketmq_error::RUNTIME_INTERNAL_FAILURE,
RuntimeErrorKind::Internal,
operation,
"runtime",
)
}
}
fn runtime_context(operation: RuntimeOperation, source_present: bool) -> ErrorContext {
let context = ErrorContext::new().with_text(
rocketmq_error::fields::OPERATION_DIAGNOSTIC,
operation.diagnostic_label(),
);
if source_present {
context.with_secret_presence(rocketmq_error::fields::SOURCE_PRESENT)
} else {
context
}
}
impl fmt::Display for RuntimeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.error.as_ref(), formatter)
}
}
impl fmt::Debug for RuntimeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("RuntimeError")
.field("code", &self.code())
.field("condition", &self.condition())
.field("kind", &self.kind)
.field("operation", &self.operation)
.field("component", &self.component)
.field("has_source", &self.error.source().is_some())
.finish()
}
}
impl Error for RuntimeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.error.source()
}
}
impl From<std::io::Error> for RuntimeError {
#[track_caller]
fn from(source: std::io::Error) -> Self {
Self::io(RuntimeOperation::ReadFile, source)
}
}
#[cfg(test)]
mod tests {
use std::error::Error as _;
use std::io;
use rocketmq_error::CanonicalCondition;
use rocketmq_error::RecoveryHint;
use super::*;
const PERSIST_TEST_METADATA: RuntimeOperation = RuntimeOperation::external("persist-runtime-metadata");
const TEST_FAILURE: RuntimeOperation = RuntimeOperation::external("test-runtime-failure");
#[test]
fn operational_runtime_error_preserves_source_and_catalog_metadata_without_rendering_it() {
const SENTINEL: &str = "runtime-source-secret";
let error = RuntimeError::io(PERSIST_TEST_METADATA, io::Error::other(SENTINEL));
assert_eq!(error.code(), rocketmq_error::RUNTIME_IO_FAILED.code());
assert_eq!(error.condition(), CanonicalCondition::Internal);
assert_eq!(error.recovery_hint(), RecoveryHint::OperatorAction);
assert_eq!(error.operation(), PERSIST_TEST_METADATA);
assert_eq!(error.component(), "runtime");
assert!(error
.source()
.and_then(|source| source.downcast_ref::<io::Error>())
.is_some());
assert!(!error.to_string().contains(SENTINEL));
assert!(!format!("{error:?}").contains(SENTINEL));
assert!(error.public_view().is_ok());
assert!(error.diagnostic_view().is_ok());
}
#[test]
fn runtime_configuration_facade_uses_corrected_boundary_projection() {
let error = RuntimeError::configuration(RuntimeOperation::LoadConfigFile);
let public = error.public_view().expect("valid runtime public view");
let projection = public.projection();
assert_eq!("runtime.configuration.failed", public.code().as_str());
assert_eq!(rocketmq_error::HttpStatusCode::BAD_REQUEST, projection.http().status);
assert_eq!(rocketmq_error::CliExitCode::CONFIG, projection.cli().exit_code);
}
#[test]
fn runtime_clone_shares_source_location_and_backtrace() {
let caller_line = line!() + 1;
let error = RuntimeError::internal(PERSIST_TEST_METADATA, io::Error::other("typed leaf"));
let cloned = error.clone();
assert!(Arc::ptr_eq(&error.error, &cloned.error));
assert!(std::ptr::eq(
error.source().expect("runtime source"),
cloned.source().expect("cloned runtime source")
));
assert!(error
.source()
.and_then(|source| source.downcast_ref::<io::Error>())
.is_some());
assert_eq!(error.location().file(), file!());
assert_eq!(error.location().line(), caller_line);
match (error.backtrace(), cloned.backtrace()) {
(Some(left), Some(right)) => assert!(std::ptr::eq(left, right)),
(None, None) => {}
_ => panic!("a clone must share the canonical backtrace state"),
}
}
#[test]
fn operation_debug_output_is_closed_for_sentinel_control_and_unbounded_text() {
let error = RuntimeError::internal_failure(TEST_FAILURE);
let debug = format!("{error:?}");
let control = "\u{0000}\u{001b}[31m";
let unbounded = "unbounded-operation-".repeat(4096);
assert!(debug.contains("test-runtime-failure"));
assert!(!debug.contains("runtime-source-secret"));
assert!(!debug.contains("operation_diagnostic"));
assert!(!debug.contains(control));
assert!(!debug.contains(&unbounded));
let constructor: fn(RuntimeOperation) -> RuntimeError = RuntimeError::internal_failure;
assert_eq!(constructor(TEST_FAILURE).operation(), TEST_FAILURE);
}
#[test]
fn an_external_operation_reports_its_own_label() {
const INITIALIZE_COMPONENT: RuntimeOperation = RuntimeOperation::external("initialize-component-2");
assert_eq!(INITIALIZE_COMPONENT.to_string(), "initialize-component-2");
assert_eq!(
INITIALIZE_COMPONENT,
RuntimeOperation::External(ExternalOperation::new("initialize-component-2"))
);
assert_ne!(INITIALIZE_COMPONENT, RuntimeOperation::external("start-component"));
let error = RuntimeError::closed(INITIALIZE_COMPONENT);
assert_eq!(error.operation(), INITIALIZE_COMPONENT);
assert_eq!(error.kind(), RuntimeErrorKind::Closed);
}
#[test]
fn external_operation_labels_accept_only_bounded_kebab_case() {
let longest = "a".repeat(ExternalOperation::MAX_LABEL_BYTES);
assert!(is_valid_external_label(&longest));
assert!(is_valid_external_label("admit-kv-mutation-bytes"));
assert!(is_valid_external_label("ha-runtime"));
let too_long = "a".repeat(ExternalOperation::MAX_LABEL_BYTES + 1);
for invalid in [
"",
too_long.as_str(),
"-leading",
"trailing-",
"Upper-case",
"under_score",
"white space",
"control\u{001b}",
"path/segment",
"non-ascii-é",
] {
assert!(!is_valid_external_label(invalid), "{invalid:?} must be rejected");
}
}
#[test]
#[should_panic(expected = "external operation labels")]
fn a_runtime_constructed_invalid_label_is_rejected() {
let _ = ExternalOperation::new("Invalid Label");
}
#[test]
fn each_constructor_reports_a_matchable_kind() {
let cases = [
(
RuntimeError::configuration(RuntimeOperation::LoadConfigFile),
RuntimeErrorKind::Configuration,
),
(
RuntimeError::build(RuntimeOperation::BuildTokioRuntime, io::Error::other("build")),
RuntimeErrorKind::Io,
),
(
RuntimeError::io(RuntimeOperation::ReadFile, io::Error::other("read")),
RuntimeErrorKind::Io,
),
(
RuntimeError::context_unavailable(RuntimeOperation::RuntimeContext),
RuntimeErrorKind::ContextUnavailable,
),
(
RuntimeError::closed(RuntimeOperation::SpawnTaskGroupTask),
RuntimeErrorKind::Closed,
),
(
RuntimeError::poisoned(RuntimeOperation::SpawnTaskGroupTask),
RuntimeErrorKind::Poisoned,
),
(
RuntimeError::capacity(RuntimeOperation::BlockingQueueAdmission),
RuntimeErrorKind::CapacityExhausted,
),
(
RuntimeError::timed_out(RuntimeOperation::BlockingTask),
RuntimeErrorKind::TimedOut,
),
(
RuntimeError::unsupported(RuntimeOperation::RegisterScheduledTask),
RuntimeErrorKind::Unsupported,
),
(RuntimeError::internal_failure(TEST_FAILURE), RuntimeErrorKind::Internal),
];
for (error, kind) in cases {
assert_eq!(error.kind(), kind, "{error:?}");
}
}
#[test]
fn closed_and_poisoned_keep_the_unavailable_descriptor() {
let unavailable = RuntimeError::context_unavailable(RuntimeOperation::SpawnTaskGroupTask);
for error in [
RuntimeError::closed(RuntimeOperation::SpawnTaskGroupTask),
RuntimeError::poisoned(RuntimeOperation::SpawnTaskGroupTask),
] {
assert_eq!(error.code(), unavailable.code());
assert_eq!(error.condition(), unavailable.condition());
assert_ne!(error.kind(), unavailable.kind());
}
}
#[test]
fn a_relabelled_failure_keeps_its_kind_and_cause() {
let closed = RuntimeError::closed(RuntimeOperation::SpawnTaskGroupTask);
let error = RuntimeError::within(RuntimeOperation::SpawnServiceTask, closed.clone());
assert_eq!(error.kind(), RuntimeErrorKind::Closed);
assert_eq!(error.operation(), RuntimeOperation::SpawnServiceTask);
assert_eq!(error.code(), closed.code());
assert_eq!(error.component(), closed.component());
let source = error
.source()
.and_then(|source| source.downcast_ref::<RuntimeError>())
.expect("the original failure is the typed cause");
assert_eq!(source.operation(), RuntimeOperation::SpawnTaskGroupTask);
}
#[test]
fn contract_violations_stay_outside_the_operational_runtime_error_channel() {
let violation = RuntimeContractViolation::PermitTargetInDifferentTree;
assert_eq!(violation.condition(), CanonicalCondition::InvalidArgument);
assert_eq!(
violation.to_string(),
"resource permit target belongs to a different budget tree"
);
let closed_policy = RuntimeContractViolation::InvalidConfiguration {
policy: RuntimeContractPolicy::WorkerThreadsPositive,
};
assert_eq!(
closed_policy.to_string(),
"runtime configuration violates worker-threads-positive"
);
}
}