use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use tokio::sync::OnceCell;
use zeromq::prelude::*;
use zeromq::{Socket, ZmqError as WireError};
use crate::endpoint::{Role, ZmqEndpoint};
use crate::error::{ZmqError, box_err};
pub(crate) const SEND_RETRY_WINDOW: Duration = Duration::from_secs(5);
pub(crate) const SEND_RETRY_STEP: Duration = Duration::from_millis(50);
#[derive(Debug)]
pub(crate) struct Lifecycle {
pub(crate) endpoint: ZmqEndpoint,
pub(crate) resolved: OnceCell<String>,
pub(crate) closed: AtomicBool,
}
impl Lifecycle {
pub(crate) fn new(endpoint: ZmqEndpoint) -> Self {
Self {
endpoint,
resolved: OnceCell::new(),
closed: AtomicBool::new(false),
}
}
pub(crate) fn ensure_open(&self) -> Result<(), ZmqError> {
if self.closed.load(Ordering::Acquire) {
return Err(ZmqError::NotConnected);
}
Ok(())
}
pub(crate) async fn attach_receiver<S: Socket>(&self, socket: &mut S) -> Result<(), ZmqError> {
match self.endpoint.role {
Role::Bind => {
let resolved =
socket
.bind(self.endpoint.address())
.await
.map_err(|e| ZmqError::Endpoint {
endpoint: self.endpoint.address().to_owned(),
source: box_err(e),
})?;
let _ = self.resolved.set(resolved.to_string());
}
Role::Connect => {
socket
.connect(self.endpoint.address())
.await
.map_err(|e| ZmqError::Endpoint {
endpoint: self.endpoint.address().to_owned(),
source: box_err(e),
})?;
}
}
Ok(())
}
pub(crate) fn sender_address(&self) -> Result<(String, Role), ZmqError> {
match self.endpoint.role {
Role::Connect => Ok((self.endpoint.address().to_owned(), Role::Connect)),
Role::Bind => self.resolved.get().map_or_else(
|| Ok((self.endpoint.address().to_owned(), Role::Bind)),
|resolved| Ok((resolved.clone(), Role::Connect)),
),
}
}
pub(crate) async fn attach_sender<S: Socket>(&self, socket: &mut S) -> Result<(), ZmqError> {
let (address, role) = self.sender_address()?;
let outcome = match role {
Role::Bind => socket.bind(&address).await.map(|_| ()),
Role::Connect => socket.connect(&address).await,
};
outcome.map_err(|e| ZmqError::Endpoint {
endpoint: address,
source: box_err(e),
})
}
}
pub(crate) async fn send_with_retry<S: SocketSend>(
socket: &mut S,
name: &str,
message: zeromq::ZmqMessage,
) -> Result<(), ZmqError> {
let mut pending = message;
let deadline = tokio::time::Instant::now() + SEND_RETRY_WINDOW;
loop {
match socket.send(pending).await {
Ok(()) => return Ok(()),
Err(WireError::ReturnToSender { message, .. }) => {
if tokio::time::Instant::now() >= deadline {
return Err(ZmqError::Send {
name: name.to_owned(),
reason: "no connected peer".to_owned(),
});
}
pending = message;
tokio::time::sleep(SEND_RETRY_STEP).await;
}
Err(err) => {
return Err(ZmqError::Send {
name: name.to_owned(),
reason: err.to_string(),
});
}
}
}
}
pub(crate) struct DriverHandle {
pub(crate) task: tokio::task::JoinHandle<()>,
}
impl Drop for DriverHandle {
fn drop(&mut self) {
self.task.abort();
}
}
pub(crate) type SharedLifecycle = Arc<Lifecycle>;