pub use self::ZmqFanoutPublish as Publish;
pub mod prelude {
pub use ruststream::prelude::*;
pub use crate::endpoint::ZmqEndpoint;
pub use super::{Publish, ZmqFanout};
}
use std::fmt;
use std::future::{Future, ready};
use std::marker::PhantomData;
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use futures::lock::Mutex;
#[cfg(feature = "asyncapi")]
use ruststream::asyncapi::Bindings;
use ruststream::{
AddressedCopies, Broker, BytesMut, ConnectedBroker, DefaultPublish, DescribeServer,
NamedCopies, OutgoingMessage, PairError, PublishPolicy, Publisher, ServerSpec, Subscribe, Take,
};
use tokio::sync::OnceCell;
use tokio::time::timeout;
use zeromq::prelude::*;
use zeromq::{PubSocket, SubSocket, XPubSocket, ZmqError as WireError};
#[cfg(feature = "asyncapi")]
use crate::bindings::{self, SocketPair};
#[cfg(feature = "testing")]
use crate::common::DeliveryReceiver;
use crate::common::{
DEFAULT_READ_AHEAD, DriverHandle, Lifecycle, SEND_RETRY_WINDOW, SendTarget, Sender,
SharedLifecycle, attach_to, delivery_channel, dials_the_sender, send_with_retry,
};
use crate::endpoint::{Bind, Connect, Endpoint, EndpointRole, ZmqEndpoint};
use crate::error::{ZmqError, box_err};
#[cfg(feature = "testing")]
use crate::in_process::Pick;
use crate::queue::ZmqSubscriber;
use crate::wire;
#[cfg(not(feature = "testing"))]
const _: () = assert!(size_of::<Sender<PubSide>>() == size_of::<PubSide>());
#[must_use]
pub struct ZmqFanout<Role = Bind> {
endpoint: Endpoint,
read_ahead: NonZeroUsize,
cell: Arc<OnceCell<SharedLifecycle>>,
role: PhantomData<fn() -> Role>,
}
impl<Role: EndpointRole> ZmqFanout<Role> {
pub fn new(endpoint: ZmqEndpoint<Role>) -> Self {
Self {
endpoint: endpoint.into_inner(),
read_ahead: DEFAULT_READ_AHEAD,
cell: Arc::new(OnceCell::new()),
role: PhantomData,
}
}
}
impl<Role> ZmqFanout<Role> {
pub const fn read_ahead(mut self, read_ahead: NonZeroUsize) -> Self {
self.read_ahead = read_ahead;
self
}
#[must_use]
pub fn publisher(&self) -> ZmqFanoutPublisher {
ZmqFanoutPublisher {
cell: Arc::clone(&self.cell),
socket: Arc::new(Mutex::new(None)),
}
}
}
impl<Role> Clone for ZmqFanout<Role> {
fn clone(&self) -> Self {
Self {
endpoint: self.endpoint.clone(),
read_ahead: self.read_ahead,
cell: Arc::clone(&self.cell),
role: PhantomData,
}
}
}
impl<Role> fmt::Debug for ZmqFanout<Role> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZmqFanout")
.field("endpoint", &self.endpoint)
.finish_non_exhaustive()
}
}
impl<Role: EndpointRole> Broker for ZmqFanout<Role> {
type Error = ZmqError;
type Connected = ConnectedZmqFanout<Role>;
async fn connect(self) -> Result<Self::Connected, Self::Error> {
self.connect_with(Lifecycle::new).await
}
}
impl<Role: EndpointRole> ZmqFanout<Role> {
pub(crate) async fn connect_with(
self,
lifecycle: fn(Endpoint) -> Lifecycle,
) -> Result<ConnectedZmqFanout<Role>, ZmqError> {
let lifecycle = self
.cell
.get_or_try_init(async || {
self.endpoint.validate()?;
Ok::<_, ZmqError>(Arc::new(lifecycle(self.endpoint.clone())))
})
.await?
.clone();
Ok(ConnectedZmqFanout {
lifecycle,
read_ahead: self.read_ahead,
cell: self.cell,
role: PhantomData,
})
}
}
impl<Role: EndpointRole> DescribeServer for ZmqFanout<Role> {
fn describe_server(&self) -> ServerSpec {
self.endpoint.server_spec()
}
}
pub struct ConnectedZmqFanout<Role = Bind> {
lifecycle: SharedLifecycle,
read_ahead: NonZeroUsize,
cell: Arc<OnceCell<SharedLifecycle>>,
role: PhantomData<fn() -> Role>,
}
impl<Role> fmt::Debug for ConnectedZmqFanout<Role> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConnectedZmqFanout")
.field("lifecycle", &self.lifecycle)
.finish_non_exhaustive()
}
}
impl<Role> ConnectedZmqFanout<Role> {
#[cfg(feature = "testing")]
pub(crate) fn lifecycle(&self) -> &Lifecycle {
&self.lifecycle
}
#[must_use]
pub fn bound_address(&self) -> Option<String> {
self.lifecycle.bound_address()
}
#[must_use]
pub fn publisher(&self) -> ZmqFanoutPublisher {
ZmqFanoutPublisher {
cell: Arc::clone(&self.cell),
socket: Arc::new(Mutex::new(None)),
}
}
async fn open(&self, name: &str) -> Result<ZmqSubscriber, ZmqError> {
self.lifecycle.ensure_open()?;
#[cfg(feature = "testing")]
if let Some(bus) = self.lifecycle.in_process_bus() {
let slot = self.lifecycle.attach_in_process(name).await?;
let (rx, registration) = bus.subscribe(name, wire::read_delivery);
return Ok(ZmqSubscriber::from_parts(
name.to_owned(),
DeliveryReceiver::InProcess(rx),
DriverHandle::InProcess(registration, slot),
));
}
let lifecycle = Arc::clone(&self.lifecycle);
let subscription = name.to_owned();
let (mut socket, slot) = self
.lifecycle
.on_runtime(async move {
let mut socket = SubSocket::new();
let slot = lifecycle
.attach_receiver(&mut socket, &subscription)
.await?;
socket
.subscribe(&subscription)
.await
.map_err(|e| ZmqError::Receive(e.to_string()))?;
Ok((socket, slot))
})
.await?;
let (tx, rx) = delivery_channel(self.read_ahead);
let task = self.lifecycle.spawn(async move {
loop {
match socket.recv().await {
Ok(message) => {
let Some(item) = wire::read_delivery(message) else {
continue;
};
if tx.send(item).await.is_err() {
break;
}
}
Err(err) => {
if tx
.send(Err(ZmqError::Receive(err.to_string())))
.await
.is_err()
{
break;
}
}
}
}
});
Ok(ZmqSubscriber::from_parts(
name.to_owned(),
rx,
DriverHandle::Task(task, slot),
))
}
}
impl<Role: EndpointRole> ConnectedBroker for ConnectedZmqFanout<Role> {
type Error = ZmqError;
type Closed = ();
fn shutdown(self) -> impl Future<Output = Result<(), Self::Error>> {
self.lifecycle.closed.store(true, Ordering::Release);
ready(Ok(()))
}
}
impl Subscribe for ConnectedZmqFanout<Bind> {
type Subscriber = ZmqSubscriber;
type Copies = AddressedCopies;
async fn subscribe(&self, name: &str) -> Result<Self::Subscriber, Self::Error> {
self.open(name).await
}
}
impl Subscribe for ConnectedZmqFanout<Connect> {
type Subscriber = ZmqSubscriber;
type Copies = NamedCopies;
async fn subscribe(&self, name: &str) -> Result<Self::Subscriber, Self::Error> {
self.open(name).await
}
}
#[derive(Clone)]
pub struct ZmqFanoutPublisher {
cell: Arc<OnceCell<SharedLifecycle>>,
socket: Arc<Mutex<Option<Sender<PubSide>>>>,
}
enum PubSide {
Remote(PubSocket),
Loopback(XPubSocket),
}
impl fmt::Debug for ZmqFanoutPublisher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZmqFanoutPublisher").finish_non_exhaustive()
}
}
impl Publisher for ZmqFanoutPublisher {
type Payload = Take;
type Error = ZmqError;
type Options = ();
#[allow(clippy::significant_drop_tightening)]
async fn publish(
&self,
msg: OutgoingMessage<'_, BytesMut>,
_options: Option<&Self::Options>,
) -> Result<(), Self::Error> {
let lifecycle = self.cell.get().ok_or(ZmqError::NotConnected)?;
lifecycle.ensure_open()?;
let (name, payload, headers) = msg.into_parts();
let frames = wire::encode_to(name, name, &headers, payload.freeze())?;
if let Some(subscription) = lifecycle.dialer() {
return Err(dials_the_sender(name, subscription, "PUB", "fan-out"));
}
let mut guard = self.socket.lock().await;
if guard.is_none() {
*guard = Some(attach(lifecycle).await?);
}
match guard.as_mut().expect("just attached") {
Sender::Socket(PubSide::Remote(socket)) => send_with_retry(socket, name, frames).await,
Sender::Socket(PubSide::Loopback(socket)) => {
send_with_retry(socket, name, frames).await
}
#[cfg(feature = "testing")]
Sender::InProcess { bus, local } => {
let pick = if *local {
Pick::Prefix(name)
} else {
Pick::Nobody
};
bus.send(name, frames, None, pick);
Ok(())
}
}
}
}
async fn attach(lifecycle: &SharedLifecycle) -> Result<Sender<PubSide>, ZmqError> {
#[cfg(feature = "testing")]
if let Some(bus) = lifecycle.in_process_bus() {
return Ok(Sender::InProcess {
bus: Arc::clone(bus),
local: lifecycle.local_listener().is_some(),
});
}
let attaching = Arc::clone(lifecycle);
let socket = lifecycle
.on_runtime(async move {
match attaching.send_target() {
target @ SendTarget::Local(_) => {
let address = target.address().to_owned();
let mut socket = XPubSocket::new();
attach_to(&mut socket, target).await?;
await_filter(&mut socket, &address).await?;
Ok(PubSide::Loopback(socket))
}
target => {
let mut socket = PubSocket::new();
attach_to(&mut socket, target).await?;
Ok(PubSide::Remote(socket))
}
}
})
.await?;
Ok(Sender::Socket(socket))
}
async fn await_filter(socket: &mut XPubSocket, address: &str) -> Result<(), ZmqError> {
timeout(SEND_RETRY_WINDOW, socket.recv())
.await
.unwrap_or(Err(WireError::Other("the subscription sent no filter")))
.map(drop)
.map_err(|err| ZmqError::Endpoint {
endpoint: address.to_owned(),
source: box_err(err),
})
}
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct ZmqFanoutPublish;
impl<Role: EndpointRole> PublishPolicy<ConnectedZmqFanout<Role>> for ZmqFanoutPublish {
type Live = ZmqFanoutPublisher;
fn pair(
self,
connected: &ConnectedZmqFanout<Role>,
) -> impl Future<Output = Result<Self::Live, PairError>> {
ready(Ok(connected.publisher()))
}
#[cfg(feature = "asyncapi")]
fn channel_bindings(&self, channel: &str) -> Bindings {
bindings::channel(SocketPair::PubSub, channel)
}
}
impl<Role: EndpointRole> DefaultPublish for ConnectedZmqFanout<Role> {
type Policy = ZmqFanoutPublish;
}