use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use super::NULL_TOPIC;
pub(crate) const NOTIFICATIONS_CHANNEL: &str = "dbos_notifications_channel";
pub(crate) const EVENTS_CHANNEL: &str = "dbos_workflow_events_channel";
pub(crate) const STREAMS_CHANNEL: &str = "dbos_streams_channel";
const MESSAGE_PREFIX: &str = "m";
const EVENT_PREFIX: &str = "e";
const STREAM_PREFIX: &str = "s";
pub(crate) fn message_key(destination_id: &str, topic: Option<&str>) -> String {
format!(
"{MESSAGE_PREFIX}::{destination_id}::{}",
topic.unwrap_or(NULL_TOPIC)
)
}
pub(crate) fn event_key(workflow_id: &str, key: &str) -> String {
format!("{EVENT_PREFIX}::{workflow_id}::{key}")
}
#[allow(dead_code)]
pub(crate) fn stream_key(workflow_id: &str, key: &str) -> String {
format!("{STREAM_PREFIX}::{workflow_id}::{key}")
}
pub(crate) fn key_for(channel: &str, payload: &str) -> Option<String> {
let prefix = match channel {
NOTIFICATIONS_CHANNEL => MESSAGE_PREFIX,
EVENTS_CHANNEL => EVENT_PREFIX,
STREAMS_CHANNEL => STREAM_PREFIX,
_ => return None,
};
Some(format!("{prefix}::{payload}"))
}
#[derive(Debug, Default)]
pub(crate) struct Registry {
waiters: Mutex<HashMap<String, tokio::sync::broadcast::Sender<()>>>,
}
impl Registry {
pub(crate) fn subscribe(self: &Arc<Self>, key: String) -> Subscription {
let mut waiters = self.waiters.lock().expect("registry lock");
let sender = waiters.entry(key.clone()).or_insert_with(|| {
tokio::sync::broadcast::Sender::new(1)
});
let receiver = sender.subscribe();
Subscription {
key,
registry: Arc::clone(self),
receiver,
}
}
pub(crate) fn subscribe_exclusive(self: &Arc<Self>, key: String) -> Option<Subscription> {
let mut waiters = self.waiters.lock().expect("registry lock");
if waiters.contains_key(&key) {
return None;
}
let sender = tokio::sync::broadcast::Sender::new(1);
let receiver = sender.subscribe();
waiters.insert(key.clone(), sender);
Some(Subscription {
key,
registry: Arc::clone(self),
receiver,
})
}
pub(crate) fn wake(&self, key: &str) {
let waiters = self.waiters.lock().expect("registry lock");
if let Some(sender) = waiters.get(key) {
let _ = sender.send(());
tracing::trace!(key, "woke a waiter");
}
}
pub(crate) fn wake_all(&self) {
let waiters = self.waiters.lock().expect("registry lock");
for sender in waiters.values() {
let _ = sender.send(());
}
if !waiters.is_empty() {
tracing::debug!(
waiters = waiters.len(),
"woke every waiter after a gap in notifications"
);
}
}
#[cfg(test)]
fn registered(&self) -> usize {
self.waiters.lock().expect("registry lock").len()
}
}
#[derive(Debug)]
pub(crate) struct Subscription {
key: String,
registry: Arc<Registry>,
receiver: tokio::sync::broadcast::Receiver<()>,
}
impl Subscription {
pub(crate) async fn notified(&mut self) {
let _ = self.receiver.recv().await;
}
}
impl Drop for Subscription {
fn drop(&mut self) {
let mut waiters = self.registry.waiters.lock().expect("registry lock");
if waiters
.get(&self.key)
.is_some_and(|sender| sender.receiver_count() <= 1)
{
waiters.remove(&self.key);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn a_key_is_prefixed_by_what_is_being_waited_for() {
assert_eq!(event_key("wf-1", "k"), "e::wf-1::k");
assert_eq!(stream_key("wf-1", "k"), "s::wf-1::k");
assert_ne!(event_key("wf-1", "k"), stream_key("wf-1", "k"));
}
#[test]
fn a_message_key_resolves_an_absent_topic_to_the_sentinel() {
assert_eq!(message_key("wf-1", Some("orders")), "m::wf-1::orders");
assert_eq!(message_key("wf-1", None), format!("m::wf-1::{NULL_TOPIC}"));
}
#[test]
fn a_listener_reaches_a_waiter_by_concatenation_never_by_parsing() {
let wire = |id: &str, key: &str| format!("{id}::{key}");
for (id, key) in [
("wf-1", "progress"),
("wf::with::colons", "k"),
("wf-1", "key::with::colons"),
("::leading", "trailing::"),
] {
let payload = wire(id, key);
assert_eq!(event_key(id, key), format!("e::{payload}"));
assert_eq!(stream_key(id, key), format!("s::{payload}"));
assert_eq!(
message_key(id, Some(key)),
format!("m::{payload}"),
"a listener must reach the waiter by prefixing the payload verbatim"
);
assert_eq!(
key_for(NOTIFICATIONS_CHANNEL, &payload).as_deref(),
Some(message_key(id, Some(key)).as_str())
);
assert_eq!(
key_for(EVENTS_CHANNEL, &payload).as_deref(),
Some(event_key(id, key).as_str())
);
assert_eq!(
key_for(STREAMS_CHANNEL, &payload).as_deref(),
Some(stream_key(id, key).as_str())
);
}
let sentinel_payload = wire("wf-1", NULL_TOPIC);
assert_eq!(message_key("wf-1", None), format!("m::{sentinel_payload}"));
assert_eq!(
key_for(NOTIFICATIONS_CHANNEL, &sentinel_payload).as_deref(),
Some(message_key("wf-1", None).as_str()),
"a message with no topic is reached the same way as any other",
);
}
#[test]
fn an_unknown_channel_names_no_key() {
assert_eq!(key_for("some_other_channel", "wf-1::k"), None);
assert_eq!(key_for("", "wf-1::k"), None);
}
#[tokio::test]
async fn colliding_keys_only_cost_a_spurious_wakeup() {
let registry = Arc::new(Registry::default());
assert_eq!(
message_key("a", Some("b::c")),
message_key("a::b", Some("c"))
);
let mut waiter = registry.subscribe(message_key("a", Some("b::c")));
registry.wake(&message_key("a::b", Some("c")));
tokio::time::timeout(Duration::from_secs(5), waiter.notified())
.await
.expect("a colliding key should still wake, harmlessly");
}
#[tokio::test]
async fn a_second_exclusive_waiter_is_refused_until_the_first_is_gone() {
let registry = Arc::new(Registry::default());
let key = message_key("wf-1", Some("orders"));
let first = registry
.subscribe_exclusive(key.clone())
.expect("nothing was waiting");
assert!(
registry.subscribe_exclusive(key.clone()).is_none(),
"a second receiver on one topic must be refused, not queued behind the first",
);
drop(first);
assert_eq!(registry.registered(), 0);
assert!(
registry.subscribe_exclusive(key).is_some(),
"the topic is free again once its receiver has gone",
);
}
#[tokio::test]
async fn exclusivity_covers_one_key_and_no_more() {
let registry = Arc::new(Registry::default());
let _orders = registry
.subscribe_exclusive(message_key("wf-1", Some("orders")))
.expect("nothing was waiting");
let _refunds = registry
.subscribe_exclusive(message_key("wf-1", Some("refunds")))
.expect("another topic on the same workflow is a different wait");
let _elsewhere = registry
.subscribe_exclusive(message_key("wf-2", Some("orders")))
.expect("another workflow on the same topic is a different wait");
let _event = registry.subscribe(event_key("wf-1", "orders"));
assert_eq!(registry.registered(), 4, "four distinct waits, four keys");
}
#[tokio::test]
async fn an_exclusive_waiter_and_a_shared_one_do_not_share_a_key() {
let registry = Arc::new(Registry::default());
let key = message_key("wf-1", None);
let shared = registry.subscribe(key.clone());
assert!(registry.subscribe_exclusive(key.clone()).is_none());
drop(shared);
let _exclusive = registry
.subscribe_exclusive(key.clone())
.expect("free again");
let _joined = registry.subscribe(key);
assert_eq!(registry.registered(), 1);
}
#[tokio::test]
async fn a_subscriber_is_woken_and_deregisters_when_dropped() {
let registry = Arc::new(Registry::default());
let mut subscription = registry.subscribe(event_key("wf-1", "progress"));
assert_eq!(registry.registered(), 1);
registry.wake(&event_key("wf-1", "progress"));
subscription.notified().await;
drop(subscription);
assert_eq!(
registry.registered(),
0,
"the last subscriber takes its registration with it"
);
}
#[tokio::test]
async fn every_waiter_on_one_key_is_woken() {
let registry = Arc::new(Registry::default());
let key = stream_key("wf-1", "log");
let mut first = registry.subscribe(key.clone());
let mut second = registry.subscribe(key.clone());
assert_eq!(registry.registered(), 1);
registry.wake(&key);
first.notified().await;
second.notified().await;
drop(first);
assert_eq!(registry.registered(), 1, "the other waiter is still here");
registry.wake(&key);
second.notified().await;
}
#[tokio::test]
async fn a_parked_waiter_is_woken_by_a_later_write() {
let registry = Arc::new(Registry::default());
let mut subscription = registry.subscribe(event_key("wf-1", "k"));
let waiting = tokio::spawn(async move { subscription.notified().await });
tokio::time::sleep(Duration::from_millis(50)).await;
registry.wake(&event_key("wf-1", "k"));
tokio::time::timeout(Duration::from_secs(5), waiting)
.await
.expect("a parked waiter was never woken")
.unwrap();
}
#[tokio::test]
async fn a_wakeup_between_subscribing_and_waiting_is_not_missed() {
let registry = Arc::new(Registry::default());
let mut subscription = registry.subscribe(event_key("wf-1", "k"));
registry.wake(&event_key("wf-1", "k"));
tokio::time::timeout(Duration::from_secs(5), subscription.notified())
.await
.expect("a wakeup during the look was dropped");
}
#[tokio::test]
async fn a_gap_wakes_every_waiter_whatever_it_waits_for() {
let registry = Arc::new(Registry::default());
let mut on_event = registry.subscribe(event_key("wf-1", "progress"));
let mut on_stream = registry.subscribe(stream_key("wf-2", "log"));
let mut on_message = registry.subscribe(message_key("wf-3", None));
registry.wake_all();
on_event.notified().await;
on_stream.notified().await;
on_message.notified().await;
}
#[test]
fn a_wake_for_nobody_is_not_an_error() {
let registry = Arc::new(Registry::default());
registry.wake(&event_key("someone-else", "key"));
registry.wake_all();
assert_eq!(registry.registered(), 0);
}
}