pub use self::ZmqQueuePublish as Publish;
pub mod prelude {
pub use ruststream::prelude::*;
pub use crate::endpoint::ZmqEndpoint;
pub use super::{Publish, ZmqQueue};
}
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::Stream;
use futures::lock::Mutex;
#[cfg(feature = "asyncapi")]
use ruststream::asyncapi::Bindings;
use ruststream::{
AddressedCopies, BatchSubscriber, Broker, BufferedSubscriber, BytesMut, ConnectedBroker,
DefaultPublish, DescribeServer, NamedCopies, OutgoingMessage, PairError, PublishPolicy,
Publisher, ServerSpec, Subscribe, Subscriber, Take,
};
use tokio::sync::OnceCell;
use zeromq::prelude::*;
use zeromq::{PullSocket, PushSocket};
#[cfg(feature = "asyncapi")]
use crate::bindings::{self, SocketPair};
use crate::common::{
BATCH_MAX_WAIT, DEFAULT_READ_AHEAD, DeliveryReceiver, DriverHandle, Lifecycle, Outbox,
OwnListener, Sender, SharedLifecycle, WireSubscriber, delivery_channel, dials_the_sender,
release, returns_to_subscription,
};
use crate::endpoint::{Bind, Connect, Endpoint, EndpointRole, ZmqEndpoint};
use crate::error::ZmqError;
#[cfg(feature = "testing")]
use crate::in_process::Pick;
use crate::message::ZmqMessage;
use crate::wire;
#[cfg(not(feature = "testing"))]
const _: () = assert!(size_of::<Sender<Outbox<PushSocket>>>() == size_of::<Outbox<PushSocket>>());
#[must_use]
pub struct ZmqQueue<Role = Bind> {
endpoint: Endpoint,
read_ahead: NonZeroUsize,
cell: Arc<OnceCell<SharedLifecycle>>,
role: PhantomData<fn() -> Role>,
}
impl<Role: EndpointRole> ZmqQueue<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> ZmqQueue<Role> {
pub const fn read_ahead(mut self, read_ahead: NonZeroUsize) -> Self {
self.read_ahead = read_ahead;
self
}
#[must_use]
pub fn publisher(&self) -> ZmqQueuePublisher {
ZmqQueuePublisher {
cell: Arc::clone(&self.cell),
push: Arc::new(Mutex::new(None)),
}
}
}
impl<Role> Clone for ZmqQueue<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 ZmqQueue<Role> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZmqQueue")
.field("endpoint", &self.endpoint)
.finish_non_exhaustive()
}
}
impl<Role: EndpointRole> Broker for ZmqQueue<Role> {
type Error = ZmqError;
type Connected = ConnectedZmqQueue<Role>;
async fn connect(self) -> Result<Self::Connected, Self::Error> {
self.connect_with(Lifecycle::new).await
}
}
impl<Role: EndpointRole> ZmqQueue<Role> {
pub(crate) async fn connect_with(
self,
lifecycle: fn(Endpoint) -> Lifecycle,
) -> Result<ConnectedZmqQueue<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(ConnectedZmqQueue {
lifecycle,
read_ahead: self.read_ahead,
cell: self.cell,
role: PhantomData,
})
}
}
impl<Role: EndpointRole> DescribeServer for ZmqQueue<Role> {
fn describe_server(&self) -> ServerSpec {
self.endpoint.server_spec()
}
}
pub struct ConnectedZmqQueue<Role = Bind> {
lifecycle: SharedLifecycle,
read_ahead: NonZeroUsize,
cell: Arc<OnceCell<SharedLifecycle>>,
role: PhantomData<fn() -> Role>,
}
impl<Role> fmt::Debug for ConnectedZmqQueue<Role> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConnectedZmqQueue")
.field("lifecycle", &self.lifecycle)
.finish_non_exhaustive()
}
}
impl<Role> ConnectedZmqQueue<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) -> ZmqQueuePublisher {
ZmqQueuePublisher {
cell: Arc::clone(&self.cell),
push: Arc::new(Mutex::new(None)),
}
}
async fn paired(&self) -> Result<ZmqQueuePublisher, PairError> {
let publisher = self.publisher();
if self.lifecycle.attaches_on_pairing() {
publisher
.attach(&self.lifecycle)
.await
.map_err(PairError::new)?;
}
Ok(publisher)
}
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 = PullSocket::new();
let slot = lifecycle
.attach_receiver(&mut socket, &subscription)
.await?;
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 ConnectedZmqQueue<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 ConnectedZmqQueue<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 ConnectedZmqQueue<Connect> {
type Subscriber = ZmqSubscriber;
type Copies = NamedCopies;
async fn subscribe(&self, name: &str) -> Result<Self::Subscriber, Self::Error> {
self.open(name).await
}
}
pub struct ZmqSubscriber {
name: String,
inner: BufferedSubscriber<WireSubscriber>,
}
impl fmt::Debug for ZmqSubscriber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZmqSubscriber")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
impl ZmqSubscriber {
pub(crate) fn from_parts(name: String, rx: DeliveryReceiver, driver: DriverHandle) -> Self {
Self {
name,
inner: BufferedSubscriber::new(WireSubscriber {
rx,
_driver: driver,
})
.max_wait(BATCH_MAX_WAIT),
}
}
}
impl Subscriber for ZmqSubscriber {
type Message = ZmqMessage;
type Error = ZmqError;
fn stream(&mut self) -> impl Stream<Item = Result<ZmqMessage, ZmqError>> + Send + '_ {
self.inner.stream()
}
}
impl BatchSubscriber for ZmqSubscriber {
type Batch = Vec<ZmqMessage>;
fn batches(
&mut self,
size: NonZeroUsize,
) -> impl Stream<Item = Result<Self::Batch, ZmqError>> + Send + '_ {
self.inner.batches(size)
}
}
#[derive(Clone)]
pub struct ZmqQueuePublisher {
cell: Arc<OnceCell<SharedLifecycle>>,
push: Arc<Mutex<Option<Attached>>>,
}
pub(crate) struct Attached {
socket: Sender<Outbox<PushSocket>>,
local: Option<String>,
dials: bool,
}
impl Attached {
async fn attach(lifecycle: &SharedLifecycle) -> Result<Self, ZmqError> {
#[cfg(feature = "testing")]
if let Some(bus) = lifecycle.in_process_bus() {
let local = lifecycle
.local_listener()
.map(|listener| listener.subscription.clone());
return Ok(Self {
socket: Sender::InProcess {
bus: Arc::clone(bus),
local: local.is_some(),
},
local,
dials: false,
});
}
let attaching = Arc::clone(lifecycle);
let (socket, local) = lifecycle
.on_runtime(async move {
let mut socket = PushSocket::new();
let local = attaching
.attach_sender(&mut socket)
.await?
.map(|listener| listener.subscription.clone());
Ok((socket, local))
})
.await?;
Ok(Self {
socket: Sender::Socket(Outbox::new(socket)),
local,
dials: lifecycle.dials(),
})
}
pub(crate) async fn give_way(self) {
match self.socket {
Sender::Socket(outbox) => {
if let Some(mut socket) = outbox.into_socket().await {
release(&mut socket).await;
}
}
#[cfg(feature = "testing")]
Sender::InProcess { .. } => {}
}
}
async fn send(&mut self, name: &str, frames: zeromq::ZmqMessage) -> Result<(), ZmqError> {
match &mut self.socket {
Sender::Socket(outbox) => outbox.send(name, frames).await,
#[cfg(feature = "testing")]
Sender::InProcess { bus, local } => {
let pick = if *local { Pick::All } else { Pick::Nobody };
bus.send(name, frames, None, pick);
Ok(())
}
}
}
}
impl ZmqQueuePublisher {
async fn attach(&self, lifecycle: &SharedLifecycle) -> Result<(), ZmqError> {
let _attaching = lifecycle.attaching().await;
if self.push.lock().await.is_some() {
return Ok(());
}
let attached = Attached::attach(lifecycle).await?;
if lifecycle.listens_here(attached.local.is_some()) {
lifecycle.listens_alongside(OwnListener::Queue(Arc::downgrade(&self.push)));
}
*self.push.lock().await = Some(attached);
Ok(())
}
}
impl fmt::Debug for ZmqQueuePublisher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZmqQueuePublisher").finish_non_exhaustive()
}
}
impl Publisher for ZmqQueuePublisher {
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, "PUSH", "queue"));
}
let mut push = self.push.lock().await;
while push.is_none() {
drop(push);
self.attach(lifecycle).await?;
push = self.push.lock().await;
}
let attached = push.as_mut().expect("the loop leaves an attached socket");
if let Some(subscription) = attached.local.as_deref()
&& subscription != name
{
return Err(returns_to_subscription(name, subscription));
}
let dials = attached.dials;
let sent = attached.send(name, frames).await;
if sent.is_err() && dials {
*push = None;
}
sent
}
}
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct ZmqQueuePublish;
impl<Role: EndpointRole> PublishPolicy<ConnectedZmqQueue<Role>> for ZmqQueuePublish {
type Live = ZmqQueuePublisher;
fn pair(
self,
connected: &ConnectedZmqQueue<Role>,
) -> impl Future<Output = Result<Self::Live, PairError>> {
connected.paired()
}
#[cfg(feature = "asyncapi")]
fn channel_bindings(&self, channel: &str) -> Bindings {
bindings::channel(SocketPair::PushPull, channel)
}
}
impl<Role: EndpointRole> DefaultPublish for ConnectedZmqQueue<Role> {
type Policy = ZmqQueuePublish;
}
#[cfg(test)]
mod tests {
use ruststream::nonzero;
use super::*;
#[tokio::test]
async fn clones_sharing_a_lifecycle_keep_their_own_read_ahead() {
let wide = ZmqQueue::new(ZmqEndpoint::bind("tcp://127.0.0.1:0"));
let narrow = wide.clone().read_ahead(nonzero!(4_usize));
let wide = wide.connect().await.expect("connects");
let narrow = narrow.connect().await.expect("connects");
assert!(
Arc::ptr_eq(&wide.lifecycle, &narrow.lifecycle),
"one lifecycle"
);
assert_eq!(wide.read_ahead, DEFAULT_READ_AHEAD);
assert_eq!(narrow.read_ahead, nonzero!(4_usize));
}
}