use std::collections::{HashMap, HashSet};
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use serde::Serialize;
use serde_json::Value;
use tokio::sync::Notify;
use tokio::sync::mpsc::error::TryRecvError;
use tokio_util::sync::CancellationToken;
use super::dispatcher::EventDispatcher;
use super::event::{Event, ExecutionId, Topic, json_len_within};
use super::pattern::{SubscriptionPattern, SubscriptionSet};
use super::queue::{QueueError, QueueLimits, QueueReceiver, QueueSender, queue};
use crate::sync::LockRecover;
const MAX_RECORDED_OVERFLOW_TOPICS: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Limits {
pub max_event_bytes: usize,
pub publish_queue: QueueLimits,
pub connection_queue: QueueLimits,
pub max_subscriptions_per_connection: usize,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_event_bytes: 1024 * 1024,
publish_queue: QueueLimits {
max_events: 1024,
max_bytes: 8 * 1024 * 1024,
},
connection_queue: QueueLimits {
max_events: 256,
max_bytes: 4 * 1024 * 1024,
},
max_subscriptions_per_connection: 64,
}
}
}
impl Limits {
fn assert_consistent(&self) {
assert!(
self.max_event_bytes <= self.publish_queue.max_bytes
&& self.max_event_bytes <= self.connection_queue.max_bytes,
"max_event_bytes must fit in every queue"
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
#[serde(transparent)]
pub struct ConnectionId(u64);
impl ConnectionId {
pub(crate) fn value(self) -> u64 {
self.0
}
}
pub(crate) fn allocate_connection_id() -> ConnectionId {
ConnectionId(NEXT_CONNECTION_ID.fetch_add(1, Ordering::Relaxed))
}
impl fmt::Display for ConnectionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisconnectReason {
SlowConsumer,
PublishQueueOverflow,
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "part of the bus API that the IPC server does not use"
)
)]
Requested,
ReceiverDropped,
Shutdown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PublishError {
TooLarge {
size_at_least: usize,
max: usize,
},
QueueOverflow,
Closed,
}
impl fmt::Display for PublishError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooLarge { max, .. } => {
write!(f, "event exceeds the maximum size of {max} bytes")
}
Self::QueueOverflow => write!(f, "event queue overflow"),
Self::Closed => write!(f, "event bus is closed"),
}
}
}
impl std::error::Error for PublishError {}
pub(super) fn check_event_size(
topic: &Topic,
data: &Value,
max: usize,
) -> Result<usize, PublishError> {
let overhead = super::event::event_size(topic.as_str().len(), 0);
max.checked_sub(overhead)
.and_then(|room| json_len_within(data, room))
.ok_or(PublishError::TooLarge {
size_at_least: max.saturating_add(1),
max,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubscribeError {
UnknownConnection,
TooManySubscriptions { max: usize },
}
impl fmt::Display for SubscribeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownConnection => write!(f, "connection is not registered"),
Self::TooManySubscriptions { max } => {
write!(f, "a connection may hold at most {max} subscriptions")
}
}
}
}
impl std::error::Error for SubscribeError {}
struct ConnectionShared {
reason: OnceLock<DisconnectReason>,
disconnected: CancellationToken,
}
struct ConnectionEntry {
subscriptions: SubscriptionSet,
sender: QueueSender,
shared: Arc<ConnectionShared>,
}
#[derive(Default)]
struct Registry {
connections: HashMap<ConnectionId, ConnectionEntry>,
shut_down: bool,
}
impl Registry {
fn remove(&mut self, id: ConnectionId, reason: DisconnectReason) -> bool {
match self.connections.remove(&id) {
Some(entry) => {
let _ = entry.shared.reason.set(reason);
entry.shared.disconnected.cancel();
log::debug!("event connection {id} disconnected: {reason:?}");
true
}
None => false,
}
}
}
#[derive(Default)]
pub(super) struct OverflowRecord {
topics: HashSet<Topic>,
everything: bool,
}
impl OverflowRecord {
fn note(&mut self, topic: &Topic) {
if self.everything {
return;
}
if self.topics.len() >= MAX_RECORDED_OVERFLOW_TOPICS && !self.topics.contains(topic) {
self.everything = true;
self.topics.clear();
} else {
self.topics.insert(topic.clone());
}
}
fn affects(&self, subscriptions: &SubscriptionSet) -> bool {
if subscriptions.is_empty() {
return false;
}
self.everything || self.topics.iter().any(|t| subscriptions.matches(t))
}
}
pub(super) struct SeqSource(Mutex<u64>);
impl SeqSource {
pub(super) const fn new() -> Self {
Self(Mutex::new(1))
}
}
static PROCESS_SEQ: SeqSource = SeqSource::new();
struct PublishState {
sender: Option<QueueSender>,
}
pub(super) struct Inner {
limits: Limits,
publish: Mutex<PublishState>,
seq: &'static SeqSource,
overflow: Mutex<OverflowRecord>,
overflow_pending: AtomicBool,
overflow_notify: Notify,
registry: Mutex<Registry>,
}
static NEXT_CONNECTION_ID: AtomicU64 = AtomicU64::new(1);
impl Inner {
pub(super) fn take_overflow(&self) -> Option<OverflowRecord> {
if !self.overflow_pending.load(Ordering::Acquire) {
return None;
}
let mut record = self.overflow.lock_recover();
self.overflow_pending.store(false, Ordering::Release);
Some(std::mem::take(&mut *record))
}
pub(super) fn disconnect_affected(&self, record: &OverflowRecord) {
let mut registry = self.registry.lock_recover();
let affected: Vec<ConnectionId> = registry
.connections
.iter()
.filter(|(_, entry)| record.affects(&entry.subscriptions))
.map(|(id, _)| *id)
.collect();
for id in affected {
registry.remove(id, DisconnectReason::PublishQueueOverflow);
}
}
pub(super) fn deliver(&self, event: &Arc<Event>) {
let mut registry = self.registry.lock_recover();
let mut failed: Vec<(ConnectionId, DisconnectReason)> = Vec::new();
for (id, entry) in ®istry.connections {
if !entry.subscriptions.matches(&event.topic) {
continue;
}
match entry.sender.try_send(event) {
Ok(()) => {}
Err(QueueError::Full) => failed.push((*id, DisconnectReason::SlowConsumer)),
Err(QueueError::Closed) => failed.push((*id, DisconnectReason::ReceiverDropped)),
}
}
for (id, reason) in failed {
registry.remove(id, reason);
}
}
pub(super) fn close_publishing(&self) {
self.publish.lock_recover().sender = None;
}
pub(super) fn disconnect_all(&self, reason: DisconnectReason) {
let mut registry = self.registry.lock_recover();
registry.shut_down = true;
let ids: Vec<ConnectionId> = registry.connections.keys().copied().collect();
for id in ids {
registry.remove(id, reason);
}
}
pub(super) fn overflow_notified(&self) -> impl Future<Output = ()> + '_ {
self.overflow_notify.notified()
}
}
#[derive(Clone)]
pub struct EventBus {
inner: Arc<Inner>,
}
impl EventBus {
pub fn new(limits: Limits) -> (Self, EventDispatcher) {
Self::with_seq_source(limits, &PROCESS_SEQ)
}
pub(super) fn with_seq_source(
limits: Limits,
seq: &'static SeqSource,
) -> (Self, EventDispatcher) {
limits.assert_consistent();
let (sender, receiver) = queue(limits.publish_queue);
let inner = Arc::new(Inner {
limits,
publish: Mutex::new(PublishState {
sender: Some(sender),
}),
seq,
overflow: Mutex::new(OverflowRecord::default()),
overflow_pending: AtomicBool::new(false),
overflow_notify: Notify::new(),
registry: Mutex::new(Registry::default()),
});
let dispatcher = EventDispatcher::new(Arc::clone(&inner), receiver);
(Self { inner }, dispatcher)
}
pub fn publish(
&self,
topic: Topic,
data: Value,
execution: Option<ExecutionId>,
) -> Result<u64, PublishError> {
let data_len = check_event_size(&topic, &data, self.inner.limits.max_event_bytes)?;
let state = self.inner.publish.lock_recover();
let mut next_seq = self.inner.seq.0.lock_recover();
let seq = *next_seq;
let event = Arc::new(Event::new(seq, topic, data, execution, data_len));
let Some(sender) = state.sender.as_ref() else {
return Err(PublishError::Closed);
};
match sender.try_send(&event) {
Ok(()) => {
*next_seq += 1;
Ok(seq)
}
Err(QueueError::Full) => {
self.inner.overflow.lock_recover().note(&event.topic);
self.inner.overflow_pending.store(true, Ordering::Release);
self.inner.overflow_notify.notify_one();
log::warn!(
"event publish queue overflow, dropped topic {}",
event.topic
);
Err(PublishError::QueueOverflow)
}
Err(QueueError::Closed) => Err(PublishError::Closed),
}
}
pub fn close(&self) {
self.inner.close_publishing();
}
pub fn register_connection(&self) -> EventStream {
let id = allocate_connection_id();
let (sender, receiver) = queue(self.inner.limits.connection_queue);
let shared = Arc::new(ConnectionShared {
reason: OnceLock::new(),
disconnected: CancellationToken::new(),
});
{
let mut registry = self.inner.registry.lock_recover();
if registry.shut_down {
let _ = shared.reason.set(DisconnectReason::Shutdown);
shared.disconnected.cancel();
} else {
registry.connections.insert(
id,
ConnectionEntry {
subscriptions: SubscriptionSet::default(),
sender,
shared: Arc::clone(&shared),
},
);
}
}
EventStream {
id,
receiver,
shared,
bus: self.clone(),
}
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "part of the bus API that the IPC server does not use"
)
)]
pub fn subscribe(
&self,
id: ConnectionId,
pattern: SubscriptionPattern,
) -> Result<bool, SubscribeError> {
let max = self.inner.limits.max_subscriptions_per_connection;
let mut registry = self.inner.registry.lock_recover();
let entry = registry
.connections
.get_mut(&id)
.ok_or(SubscribeError::UnknownConnection)?;
if entry.subscriptions.len() >= max && !entry.subscriptions.contains(&pattern) {
return Err(SubscribeError::TooManySubscriptions { max });
}
Ok(entry.subscriptions.insert(pattern))
}
pub fn subscribe_many(
&self,
id: ConnectionId,
patterns: Vec<SubscriptionPattern>,
) -> Result<usize, SubscribeError> {
let max = self.inner.limits.max_subscriptions_per_connection;
let mut registry = self.inner.registry.lock_recover();
let entry = registry
.connections
.get_mut(&id)
.ok_or(SubscribeError::UnknownConnection)?;
let fresh: HashSet<SubscriptionPattern> = patterns
.into_iter()
.filter(|pattern| !entry.subscriptions.contains(pattern))
.collect();
if entry.subscriptions.len().saturating_add(fresh.len()) > max {
return Err(SubscribeError::TooManySubscriptions { max });
}
let added = fresh.len();
for pattern in fresh {
entry.subscriptions.insert(pattern);
}
Ok(added)
}
pub fn unsubscribe(&self, id: ConnectionId, pattern: &SubscriptionPattern) -> bool {
self.inner
.registry
.lock_recover()
.connections
.get_mut(&id)
.is_some_and(|entry| entry.subscriptions.remove(pattern))
}
pub fn disconnect(&self, id: ConnectionId, reason: DisconnectReason) -> bool {
self.inner.registry.lock_recover().remove(id, reason)
}
#[cfg(test)]
pub(in crate::ipc) fn connection_count(&self) -> usize {
self.inner.registry.lock_recover().connections.len()
}
#[cfg(test)]
pub(in crate::ipc) fn subscription_count(&self, id: ConnectionId) -> Option<usize> {
self.inner
.registry
.lock_recover()
.connections
.get(&id)
.map(|entry| entry.subscriptions.len())
}
}
pub struct EventStream {
id: ConnectionId,
receiver: QueueReceiver,
shared: Arc<ConnectionShared>,
bus: EventBus,
}
impl EventStream {
pub fn id(&self) -> ConnectionId {
self.id
}
pub async fn recv(&mut self) -> Option<Arc<Event>> {
if self.shared.reason.get().is_some() {
return None;
}
self.receiver.recv().await
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "part of the bus API that the IPC server does not use"
)
)]
pub fn try_recv(&mut self) -> Result<Option<Arc<Event>>, ()> {
if self.shared.reason.get().is_some() {
return Ok(None);
}
match self.receiver.try_recv() {
Ok(event) => Ok(Some(event)),
Err(TryRecvError::Empty) => Err(()),
Err(TryRecvError::Disconnected) => Ok(None),
}
}
pub fn disconnected_token(&self) -> CancellationToken {
self.shared.disconnected.clone()
}
pub fn disconnect_reason(&self) -> Option<DisconnectReason> {
self.shared.reason.get().copied()
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "part of the bus API that the IPC server does not use"
)
)]
pub fn disconnect(&self) -> bool {
self.bus.disconnect(self.id, DisconnectReason::Requested)
}
}
impl Drop for EventStream {
fn drop(&mut self) {
self.bus
.disconnect(self.id, DisconnectReason::ReceiverDropped);
}
}