use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct ServiceInstanceId(u64);
static NEXT_SERVICE_INSTANCE_ID: AtomicU64 = AtomicU64::new(1);
impl ServiceInstanceId {
pub const DISABLED: Self = Self(0);
#[must_use]
pub fn mint() -> Self {
Self(NEXT_SERVICE_INSTANCE_ID.fetch_add(1, Ordering::Relaxed))
}
#[must_use]
pub fn get(self) -> u64 {
self.0
}
}
pub trait ServiceIdentity {
fn instance_id(&self) -> ServiceInstanceId;
}
pub trait ShutdownService {
fn shutdown(&self);
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum ServiceModeLabel {
Disabled,
Owned,
Shared,
}
pub enum ServiceMode<T> {
Disabled,
Owned(T),
Shared(Arc<T>),
}
impl<T> ServiceMode<T> {
#[must_use]
pub fn label(&self) -> ServiceModeLabel {
match self {
ServiceMode::Disabled => ServiceModeLabel::Disabled,
ServiceMode::Owned(_) => ServiceModeLabel::Owned,
ServiceMode::Shared(_) => ServiceModeLabel::Shared,
}
}
#[must_use]
pub fn service(&self) -> Option<&T> {
match self {
ServiceMode::Disabled => None,
ServiceMode::Owned(service) => Some(service),
ServiceMode::Shared(service) => Some(service),
}
}
}
impl<T: ServiceIdentity> ServiceMode<T> {
#[must_use]
pub fn instance_id(&self) -> ServiceInstanceId {
match self {
ServiceMode::Disabled => ServiceInstanceId::DISABLED,
ServiceMode::Owned(service) => service.instance_id(),
ServiceMode::Shared(service) => service.instance_id(),
}
}
}
impl<T: ShutdownService> ServiceMode<T> {
pub fn shutdown_if_owned(&mut self) {
match std::mem::replace(self, ServiceMode::Disabled) {
ServiceMode::Owned(service) => service.shutdown(),
ServiceMode::Shared(reference) => drop(reference),
ServiceMode::Disabled => {}
}
}
pub fn shutdown_owned(&self) {
if let ServiceMode::Owned(service) = self {
service.shutdown();
}
}
}
#[cfg(test)]
mod tests {
use super::{
ServiceIdentity, ServiceInstanceId, ServiceMode, ServiceModeLabel, ShutdownService,
};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct TestService {
id: ServiceInstanceId,
shutdowns: Arc<AtomicUsize>,
}
impl TestService {
fn new() -> (Self, Arc<AtomicUsize>) {
let shutdowns = Arc::new(AtomicUsize::new(0));
(
Self {
id: ServiceInstanceId::mint(),
shutdowns: Arc::clone(&shutdowns),
},
shutdowns,
)
}
}
impl ServiceIdentity for TestService {
fn instance_id(&self) -> ServiceInstanceId {
self.id
}
}
impl ShutdownService for TestService {
fn shutdown(&self) {
self.shutdowns.fetch_add(1, Ordering::AcqRel);
}
}
#[test]
fn minted_ids_are_process_unique_and_distinct_from_disabled() {
let first = ServiceInstanceId::mint();
let second = ServiceInstanceId::mint();
assert_ne!(first, second);
assert_ne!(first, ServiceInstanceId::DISABLED);
assert_ne!(second, ServiceInstanceId::DISABLED);
}
#[test]
fn shared_handle_clone_propagates_one_id_across_two_schedulers() {
let service = Arc::new(TestService::new().0);
let on_scheduler_a = ServiceMode::Shared(Arc::clone(&service));
let on_scheduler_b = ServiceMode::Shared(Arc::clone(&service));
assert_eq!(on_scheduler_a.instance_id(), on_scheduler_b.instance_id());
assert_eq!(on_scheduler_a.label(), ServiceModeLabel::Shared);
let owned_a = ServiceMode::Owned(TestService::new().0);
let owned_b = ServiceMode::Owned(TestService::new().0);
assert_ne!(owned_a.instance_id(), owned_b.instance_id());
}
#[test]
fn disabled_slot_reports_disabled_identity_and_no_service() {
let disabled: ServiceMode<TestService> = ServiceMode::Disabled;
assert_eq!(disabled.instance_id(), ServiceInstanceId::DISABLED);
assert_eq!(disabled.label(), ServiceModeLabel::Disabled);
assert!(disabled.service().is_none());
}
#[test]
fn shutdown_if_owned_stops_owned_exactly_once_and_empties_the_slot() {
let (service, shutdowns) = TestService::new();
let mut owned = ServiceMode::Owned(service);
owned.shutdown_if_owned();
assert_eq!(shutdowns.load(Ordering::Acquire), 1, "stopped once");
assert_eq!(owned.label(), ServiceModeLabel::Disabled, "slot emptied");
assert!(owned.service().is_none());
owned.shutdown_if_owned();
assert_eq!(shutdowns.load(Ordering::Acquire), 1);
}
#[test]
fn shutdown_owned_stops_only_owned_and_leaves_the_slot_readable() {
let (service, shutdowns) = TestService::new();
let owned = ServiceMode::Owned(service);
owned.shutdown_owned();
assert_eq!(shutdowns.load(Ordering::Acquire), 1, "owned stopped");
assert_eq!(owned.label(), ServiceModeLabel::Owned, "slot not emptied");
assert!(owned.service().is_some());
let (shared_service, shared_shutdowns) = TestService::new();
let shared = ServiceMode::Shared(Arc::new(shared_service));
shared.shutdown_owned();
assert_eq!(
shared_shutdowns.load(Ordering::Acquire),
0,
"a shared injection is never stopped by its consumer"
);
assert_eq!(shared.label(), ServiceModeLabel::Shared);
let disabled: ServiceMode<TestService> = ServiceMode::Disabled;
disabled.shutdown_owned();
assert_eq!(disabled.label(), ServiceModeLabel::Disabled);
}
#[test]
fn shutdown_if_owned_releases_shared_without_stopping_it() {
let (service, shutdowns) = TestService::new();
let backing = Arc::new(service);
let mut shared = ServiceMode::Shared(Arc::clone(&backing));
assert_eq!(Arc::strong_count(&backing), 2);
shared.shutdown_if_owned();
assert_eq!(Arc::strong_count(&backing), 1, "shared handle released");
assert_eq!(shutdowns.load(Ordering::Acquire), 0, "never stopped here");
assert_eq!(shared.label(), ServiceModeLabel::Disabled, "slot emptied");
let mut disabled: ServiceMode<TestService> = ServiceMode::Disabled;
disabled.shutdown_if_owned();
assert_eq!(disabled.label(), ServiceModeLabel::Disabled);
}
}