use std::fmt;
use std::sync::{
Arc, RwLock,
atomic::{AtomicU8, Ordering},
};
use crate::{
events::{Event, EventLifecycle, EventSubscription, SubscriptionLifecycleState},
operation::CancellationToken,
};
const CREATED: u8 = 0;
const ACTIVE: u8 = 1;
const CLOSED: u8 = 2;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PublisherLifecycleState {
Created,
Active,
Closed,
}
impl PublisherLifecycleState {
pub const fn is_active(self) -> bool {
matches!(self, Self::Active)
}
pub const fn is_terminal(self) -> bool {
matches!(self, Self::Closed)
}
pub const fn can_transition_to(self, next: Self) -> bool {
matches!(
(self, next),
(Self::Created, Self::Created)
| (Self::Active, Self::Active)
| (Self::Closed, Self::Closed)
| (Self::Created, Self::Active)
| (Self::Created, Self::Closed)
| (Self::Active, Self::Closed)
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PublisherLifecycleError {
from: PublisherLifecycleState,
to: PublisherLifecycleState,
}
impl PublisherLifecycleError {
pub const fn new(from: PublisherLifecycleState, to: PublisherLifecycleState) -> Self {
Self { from, to }
}
pub const fn from(self) -> PublisherLifecycleState {
self.from
}
pub const fn to(self) -> PublisherLifecycleState {
self.to
}
}
impl fmt::Display for PublisherLifecycleError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"invalid event publisher lifecycle transition from {:?} to {:?}",
self.from, self.to
)
}
}
impl std::error::Error for PublisherLifecycleError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PublisherError {
NotActive,
Closed,
EventSubsystemUnavailable,
OwnerCancelled,
SubscriptionAlreadyTerminated,
SubscriptionAlreadyRegistered,
}
impl fmt::Display for PublisherError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotActive => formatter.write_str("event publisher is not active"),
Self::Closed => formatter.write_str("event publisher is closed"),
Self::EventSubsystemUnavailable => {
formatter.write_str("event subsystem is not accepting publications")
}
Self::OwnerCancelled => formatter.write_str("event publisher owner has been cancelled"),
Self::SubscriptionAlreadyTerminated => {
formatter.write_str("event subscription is already terminated")
}
Self::SubscriptionAlreadyRegistered => {
formatter.write_str("event subscription is already registered")
}
}
}
}
impl std::error::Error for PublisherError {}
#[derive(Debug)]
pub struct Publication {
event: Arc<Event>,
subscriptions: Vec<Arc<EventSubscription>>,
}
impl Publication {
fn new(event: Arc<Event>, subscriptions: Vec<Arc<EventSubscription>>) -> Self {
Self {
event,
subscriptions,
}
}
pub fn event(&self) -> &Arc<Event> {
&self.event
}
pub fn subscriptions(&self) -> &[Arc<EventSubscription>] {
&self.subscriptions
}
pub fn subscription_count(&self) -> usize {
self.subscriptions.len()
}
pub fn is_unobserved(&self) -> bool {
self.subscriptions.is_empty()
}
}
pub struct EventPublisher {
lifecycle: AtomicU8,
event_lifecycle: Arc<EventLifecycle>,
owner_cancellation: CancellationToken,
subscriptions: RwLock<Vec<Arc<EventSubscription>>>,
}
impl fmt::Debug for EventPublisher {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("EventPublisher")
.field("lifecycle", &self.state())
.field("owner_cancelled", &self.owner_cancellation.is_cancelled())
.field("subscription_count", &self.subscription_count())
.finish_non_exhaustive()
}
}
impl EventPublisher {
pub fn new(
event_lifecycle: Arc<EventLifecycle>,
owner_cancellation: &CancellationToken,
) -> Self {
Self {
lifecycle: AtomicU8::new(CREATED),
event_lifecycle,
owner_cancellation: owner_cancellation.clone(),
subscriptions: RwLock::new(Vec::new()),
}
}
pub fn state(&self) -> PublisherLifecycleState {
match self.lifecycle.load(Ordering::Acquire) {
CREATED => PublisherLifecycleState::Created,
ACTIVE => PublisherLifecycleState::Active,
CLOSED => PublisherLifecycleState::Closed,
_ => unreachable!("event publisher contains an invalid lifecycle state"),
}
}
pub fn activate(&self) -> Result<(), PublisherLifecycleError> {
if self.owner_cancellation.is_cancelled() {
return Err(PublisherLifecycleError::new(
PublisherLifecycleState::Created,
PublisherLifecycleState::Closed,
));
}
self.transition_to(PublisherLifecycleState::Active)
}
pub fn is_active(&self) -> bool {
self.state().is_active()
}
pub fn is_closed(&self) -> bool {
self.state().is_terminal()
}
pub fn subscription_count(&self) -> usize {
self.subscriptions
.read()
.expect("event publisher subscription lock poisoned")
.len()
}
pub fn subscribe(
&self,
subscription: EventSubscription,
) -> Result<Arc<EventSubscription>, PublisherError> {
self.ensure_registration_allowed()?;
if subscription.is_terminal() || subscription.state() != SubscriptionLifecycleState::Created
{
return Err(PublisherError::SubscriptionAlreadyTerminated);
}
let subscription = Arc::new(subscription);
let mut subscriptions = self
.subscriptions
.write()
.expect("event publisher subscription lock poisoned");
match self.state() {
PublisherLifecycleState::Closed => return Err(PublisherError::Closed),
PublisherLifecycleState::Created => return Err(PublisherError::NotActive),
PublisherLifecycleState::Active => {}
}
subscriptions.push(Arc::clone(&subscription));
drop(subscriptions);
if let Err(error) = subscription.activate() {
let mut subscriptions = self
.subscriptions
.write()
.expect("event publisher subscription lock poisoned");
subscriptions.retain(|registered| !Arc::ptr_eq(registered, &subscription));
return Err(match error.from() {
SubscriptionLifecycleState::Cancelled | SubscriptionLifecycleState::Closed => {
PublisherError::SubscriptionAlreadyTerminated
}
_ => PublisherError::SubscriptionAlreadyTerminated,
});
}
Ok(subscription)
}
pub fn unsubscribe(&self, subscription: &Arc<EventSubscription>) -> bool {
let removed = {
let mut subscriptions = self
.subscriptions
.write()
.expect("event publisher subscription lock poisoned");
let original_len = subscriptions.len();
subscriptions.retain(|registered| !Arc::ptr_eq(registered, subscription));
subscriptions.len() != original_len
};
if removed {
let _ = subscription.close();
}
removed
}
pub fn publish(&self, event: Event) -> Result<Publication, PublisherError> {
self.ensure_publication_allowed()?;
let event = Arc::new(event);
let subscriptions = {
let subscriptions = self
.subscriptions
.read()
.expect("event publisher subscription lock poisoned");
match self.state() {
PublisherLifecycleState::Closed => return Err(PublisherError::Closed),
PublisherLifecycleState::Created => return Err(PublisherError::NotActive),
PublisherLifecycleState::Active => {}
}
subscriptions
.iter()
.filter(|subscription| {
subscription.matches(event.event_name(), event.event_type(), event.scope())
})
.cloned()
.collect()
};
Ok(Publication::new(event, subscriptions))
}
pub fn close(&self) {
let subscriptions = {
let mut registered = self
.subscriptions
.write()
.expect("event publisher subscription lock poisoned");
if self.lifecycle.swap(CLOSED, Ordering::AcqRel) == CLOSED {
return;
}
std::mem::take(&mut *registered)
};
for subscription in subscriptions {
let _ = subscription.cancel();
}
}
fn ensure_registration_allowed(&self) -> Result<(), PublisherError> {
self.refresh_owner_state();
match self.state() {
PublisherLifecycleState::Created => Err(PublisherError::NotActive),
PublisherLifecycleState::Closed => Err(PublisherError::Closed),
PublisherLifecycleState::Active => {
if !self.event_lifecycle.allows_publication() {
Err(PublisherError::EventSubsystemUnavailable)
} else {
Ok(())
}
}
}
}
fn ensure_publication_allowed(&self) -> Result<(), PublisherError> {
self.refresh_owner_state();
match self.state() {
PublisherLifecycleState::Created => Err(PublisherError::NotActive),
PublisherLifecycleState::Closed => Err(PublisherError::Closed),
PublisherLifecycleState::Active => {
if !self.event_lifecycle.allows_publication() {
return Err(PublisherError::EventSubsystemUnavailable);
}
Ok(())
}
}
}
fn refresh_owner_state(&self) {
if self.owner_cancellation.is_cancelled() {
self.close();
}
}
fn transition_to(&self, next: PublisherLifecycleState) -> Result<(), PublisherLifecycleError> {
loop {
let current = self.state();
if current == next {
return Ok(());
}
if !current.can_transition_to(next) {
return Err(PublisherLifecycleError::new(current, next));
}
match self.lifecycle.compare_exchange(
encode_state(current),
encode_state(next),
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return Ok(()),
Err(_) => continue,
}
}
}
}
fn encode_state(state: PublisherLifecycleState) -> u8 {
match state {
PublisherLifecycleState::Created => CREATED,
PublisherLifecycleState::Active => ACTIVE,
PublisherLifecycleState::Closed => CLOSED,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
events::{Event, EventName, Scope},
identity::EventId,
operation::CancellationToken,
};
fn event_lifecycle() -> Arc<EventLifecycle> {
Arc::new(EventLifecycle::new())
}
fn publisher() -> EventPublisher {
EventPublisher::new(event_lifecycle(), &CancellationToken::new())
}
fn event() -> Event {
Event::new(
EventId::new("event-1").unwrap(),
EventName::new("test.event").unwrap(),
"test.event",
Scope::new("engine:test").unwrap(),
)
.unwrap()
}
#[test]
fn publisher_starts_created() {
let publisher = publisher();
assert_eq!(publisher.state(), PublisherLifecycleState::Created);
assert!(!publisher.is_active());
assert!(!publisher.is_closed());
}
#[test]
fn publisher_activates() {
let publisher = publisher();
publisher.activate().unwrap();
assert_eq!(publisher.state(), PublisherLifecycleState::Active);
assert!(publisher.is_active());
}
#[test]
fn publisher_activation_is_idempotent() {
let publisher = publisher();
publisher.activate().unwrap();
publisher.activate().unwrap();
assert_eq!(publisher.state(), PublisherLifecycleState::Active);
}
#[test]
fn created_publisher_cannot_publish() {
let publisher = publisher();
assert!(matches!(
publisher.publish(event()),
Err(PublisherError::NotActive)
));
}
#[test]
fn active_publisher_can_publish() {
let publisher = publisher();
publisher.activate().unwrap();
let publication = publisher.publish(event()).unwrap();
assert_eq!(publication.event().event_id().as_str(), "event-1");
}
#[test]
fn publisher_close_is_terminal() {
let publisher = publisher();
publisher.activate().unwrap();
publisher.close();
publisher.close();
assert_eq!(publisher.state(), PublisherLifecycleState::Closed);
assert!(publisher.is_closed());
assert!(!publisher.is_active());
}
#[test]
fn closed_publisher_rejects_publication() {
let publisher = publisher();
publisher.activate().unwrap();
publisher.close();
assert!(matches!(
publisher.publish(event()),
Err(PublisherError::Closed)
));
}
#[test]
fn closed_publisher_rejects_subscription() {
let publisher = publisher();
let owner = CancellationToken::new();
publisher.activate().unwrap();
publisher.close();
let subscription = EventSubscription::new(
EventName::new("operation.completed").unwrap(),
"operation.completed",
Scope::new("engine:test").unwrap(),
|_event: &Event| {},
&owner,
)
.unwrap();
assert!(matches!(
publisher.subscribe(subscription),
Err(PublisherError::Closed)
));
}
#[test]
fn closed_publisher_recheck_prevents_late_subscription_admission() {
let publisher = publisher();
publisher.activate().unwrap();
publisher.close();
let owner = CancellationToken::new();
let subscription = EventSubscription::new(
EventName::new("operation.completed").unwrap(),
"operation.completed",
Scope::new("engine:test").unwrap(),
|_event: &Event| {},
&owner,
)
.unwrap();
assert!(matches!(
publisher.subscribe(subscription),
Err(PublisherError::Closed)
));
assert_eq!(publisher.subscription_count(), 0);
}
#[test]
fn closed_publisher_recheck_prevents_late_publication_handoff() {
let publisher = publisher();
publisher.activate().unwrap();
publisher.close();
assert!(matches!(
publisher.publish(event()),
Err(PublisherError::Closed)
));
}
#[test]
fn publisher_owner_cancellation_closes_publisher() {
let owner = CancellationToken::new();
let publisher = EventPublisher::new(event_lifecycle(), &owner);
publisher.activate().unwrap();
owner.cancel();
assert!(matches!(
publisher.publish(event()),
Err(PublisherError::Closed)
));
assert!(publisher.is_closed());
}
#[test]
fn publisher_rejects_publication_when_event_subsystem_is_draining() {
let lifecycle = event_lifecycle();
let publisher = EventPublisher::new(Arc::clone(&lifecycle), &CancellationToken::new());
publisher.activate().unwrap();
lifecycle.begin_draining();
assert!(matches!(
publisher.publish(event()),
Err(PublisherError::EventSubsystemUnavailable)
));
}
#[test]
fn publisher_rejects_publication_when_event_subsystem_is_stopped() {
let lifecycle = event_lifecycle();
let publisher = EventPublisher::new(Arc::clone(&lifecycle), &CancellationToken::new());
publisher.activate().unwrap();
lifecycle.stop();
assert!(matches!(
publisher.publish(event()),
Err(PublisherError::EventSubsystemUnavailable)
));
}
#[test]
fn subscription_registration_activates_subscription() {
let publisher = publisher();
let owner = CancellationToken::new();
publisher.activate().unwrap();
let subscription = EventSubscription::new(
EventName::new("operation.completed").unwrap(),
"operation.completed",
Scope::new("engine:test").unwrap(),
|_event: &Event| {},
&owner,
)
.unwrap();
let registered = publisher.subscribe(subscription).unwrap();
assert_eq!(registered.state(), SubscriptionLifecycleState::Active);
assert_eq!(publisher.subscription_count(), 1);
}
#[test]
fn unsubscribe_removes_subscription() {
let publisher = publisher();
let owner = CancellationToken::new();
publisher.activate().unwrap();
let subscription = EventSubscription::new(
EventName::new("operation.completed").unwrap(),
"operation.completed",
Scope::new("engine:test").unwrap(),
|_event: &Event| {},
&owner,
)
.unwrap();
let registered = publisher.subscribe(subscription).unwrap();
assert!(publisher.unsubscribe(®istered));
assert_eq!(publisher.subscription_count(), 0);
assert_eq!(registered.state(), SubscriptionLifecycleState::Closed);
}
#[test]
fn unsubscribe_unknown_subscription_returns_false() {
let publisher = publisher();
let owner = CancellationToken::new();
publisher.activate().unwrap();
let subscription = EventSubscription::new(
EventName::new("operation.completed").unwrap(),
"operation.completed",
Scope::new("engine:test").unwrap(),
|_event: &Event| {},
&owner,
)
.unwrap();
let subscription = Arc::new(subscription);
assert!(!publisher.unsubscribe(&subscription));
assert_eq!(publisher.subscription_count(), 0);
}
#[test]
fn closing_publisher_terminates_registered_subscriptions() {
let publisher = publisher();
let owner = CancellationToken::new();
publisher.activate().unwrap();
let subscription = EventSubscription::new(
EventName::new("operation.completed").unwrap(),
"operation.completed",
Scope::new("engine:test").unwrap(),
|_event: &Event| {},
&owner,
)
.unwrap();
let registered = publisher.subscribe(subscription).unwrap();
publisher.close();
assert_eq!(publisher.subscription_count(), 0);
assert!(registered.is_cancelled());
}
#[test]
fn publication_with_no_matching_subscriptions_is_valid() {
let publisher = publisher();
publisher.activate().unwrap();
let publication = publisher.publish(event()).unwrap();
assert!(publication.is_unobserved());
assert_eq!(publication.subscription_count(), 0);
}
#[test]
fn publication_preserves_one_event_identity() {
let publisher = publisher();
publisher.activate().unwrap();
let publication = publisher.publish(event()).unwrap();
assert_eq!(publication.event().event_id().as_str(), "event-1");
}
#[test]
fn publisher_lifecycle_terminal_transition_cannot_reopen() {
let publisher = publisher();
publisher.activate().unwrap();
publisher.close();
assert!(publisher.activate().is_err());
assert_eq!(publisher.state(), PublisherLifecycleState::Closed);
}
#[test]
fn concurrent_close_is_safe() {
use std::thread;
let publisher = Arc::new(publisher());
publisher.activate().unwrap();
let mut handles = Vec::new();
for _ in 0..16 {
let publisher = Arc::clone(&publisher);
handles.push(thread::spawn(move || {
publisher.close();
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(publisher.state(), PublisherLifecycleState::Closed);
assert_eq!(publisher.subscription_count(), 0);
}
}