use crate::Msg;
use crate::runtime::MailboxSender;
use crate::socket::SocketEvent;
use crate::socket::connection_iface::ISocketConnection;
use crate::socket::events::MonitorSender;
use crate::socket::options::SocketOptions;
use crate::socket::types::SocketType;
use fibre::mpmc::AsyncSender;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::Instant;
use tokio::task::JoinHandle;
#[derive(Debug)]
pub(crate) struct EndpointInfo {
pub mailbox: MailboxSender,
pub task_handle: Option<JoinHandle<()>>,
pub endpoint_type: EndpointType,
pub endpoint_uri: String,
pub pipe_ids: Option<(usize, usize)>, pub handle_id: usize,
pub target_endpoint_uri: Option<String>,
pub is_outbound_connection: bool,
pub peer_socket_type: Option<String>,
pub connection_iface: Arc<dyn ISocketConnection>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EndpointType {
Listener,
Session, }
#[derive(Debug, Clone)]
pub(crate) struct ReconnectState {
pub current_attempts: u32,
pub next_attempt_at: Option<Instant>,
}
impl Default for ReconnectState {
fn default() -> Self {
Self {
current_attempts: 0,
next_attempt_at: None,
}
}
}
impl ReconnectState {
pub fn on_connection_success(&mut self) {
self.current_attempts = 0;
self.next_attempt_at = None;
}
pub fn on_connection_failure(
&mut self,
base_ivl: std::time::Duration,
max_ivl: std::time::Duration,
) -> std::time::Duration {
let multiplier = 2u32.saturating_pow(self.current_attempts.min(31));
let mut delay = base_ivl.saturating_mul(multiplier);
if max_ivl > std::time::Duration::ZERO {
delay = delay.min(max_ivl);
}
self.current_attempts = self.current_attempts.saturating_add(1);
self.next_attempt_at = Some(Instant::now() + delay);
delay
}
pub fn is_due(&self, now: Instant) -> bool {
match self.next_attempt_at {
Some(time) => now >= time,
None => false,
}
}
}
#[derive(Debug)]
pub(crate) struct CoreState {
pub(crate) handle: usize,
pub options: Arc<SocketOptions>,
pub socket_type: SocketType,
pub pipes_tx: HashMap<usize, AsyncSender<Vec<Msg>>>,
pub pipe_reader_task_handles: HashMap<usize, JoinHandle<()>>,
pub endpoints: HashMap<String, EndpointInfo>,
pub(crate) reconnect_states: HashMap<String, ReconnectState>,
pub pipe_read_id_to_endpoint_uri: HashMap<usize, String>,
#[cfg(feature = "inproc")]
pub(crate) bound_inproc_names: HashSet<String>,
pub(crate) monitor_tx: Option<MonitorSender>,
pub(crate) last_bound_endpoint: Option<String>,
}
impl CoreState {
pub(crate) fn new(handle: usize, socket_type: SocketType, options: SocketOptions) -> Self {
Self {
handle,
options: Arc::new(options), socket_type,
pipes_tx: HashMap::new(),
pipe_reader_task_handles: HashMap::new(),
endpoints: HashMap::new(),
reconnect_states: HashMap::new(),
pipe_read_id_to_endpoint_uri: HashMap::new(),
#[cfg(feature = "inproc")]
bound_inproc_names: HashSet::new(),
monitor_tx: None,
last_bound_endpoint: None,
}
}
pub(crate) fn get_pipe_sender(&self, pipe_write_id: usize) -> Option<AsyncSender<Vec<Msg>>> {
self.pipes_tx.get(&pipe_write_id).cloned()
}
#[allow(dead_code)]
pub(crate) fn get_reader_task_handle(&self, pipe_read_id: usize) -> Option<&JoinHandle<()>> {
self.pipe_reader_task_handles.get(&pipe_read_id)
}
pub(crate) fn remove_pipe_state(&mut self, pipe_write_id: usize, pipe_read_id: usize) -> bool {
let tx_removed = self.pipes_tx.remove(&pipe_write_id).is_some();
if tx_removed {
tracing::trace!(
core_handle = self.handle,
pipe_id = pipe_write_id,
"CoreState: Removed pipe sender"
);
}
let reader_removed = if let Some(handle) = self.pipe_reader_task_handles.remove(&pipe_read_id) {
tracing::trace!(
core_handle = self.handle,
pipe_id = pipe_read_id,
"CoreState: Aborting pipe reader task"
);
handle.abort();
true
} else {
false
};
if reader_removed {
tracing::trace!(
core_handle = self.handle,
pipe_id = pipe_read_id,
"CoreState: Removed pipe reader task handle"
);
}
let map_removed = self
.pipe_read_id_to_endpoint_uri
.remove(&pipe_read_id)
.is_some();
if map_removed {
tracing::trace!(
core_handle = self.handle,
pipe_id = pipe_read_id,
"CoreState: Removed pipe_read_id_to_endpoint_uri mapping"
);
}
tx_removed || reader_removed || map_removed
}
pub(crate) fn send_monitor_event(&self, event: SocketEvent) {
if let Some(ref tx) = self.monitor_tx {
let clean = |s: String| -> String {
match s.find('#') {
Some(pos) => s[..pos].to_string(),
None => s,
}
};
let event = match event {
SocketEvent::Listening { endpoint } =>
SocketEvent::Listening { endpoint: clean(endpoint) },
SocketEvent::BindFailed { endpoint, error_msg } =>
SocketEvent::BindFailed { endpoint: clean(endpoint), error_msg },
SocketEvent::Accepted { endpoint, peer_addr } =>
SocketEvent::Accepted { endpoint: clean(endpoint), peer_addr: clean(peer_addr) },
SocketEvent::AcceptFailed { endpoint, error_msg } =>
SocketEvent::AcceptFailed { endpoint: clean(endpoint), error_msg },
SocketEvent::Connected { endpoint, peer_addr } =>
SocketEvent::Connected { endpoint: clean(endpoint), peer_addr: clean(peer_addr) },
SocketEvent::ConnectDelayed { endpoint, error_msg } =>
SocketEvent::ConnectDelayed { endpoint: clean(endpoint), error_msg },
SocketEvent::ConnectRetried { endpoint, interval } =>
SocketEvent::ConnectRetried { endpoint: clean(endpoint), interval },
SocketEvent::ConnectFailed { endpoint, error_msg } =>
SocketEvent::ConnectFailed { endpoint: clean(endpoint), error_msg },
SocketEvent::Closed { endpoint } =>
SocketEvent::Closed { endpoint: clean(endpoint) },
SocketEvent::Disconnected { endpoint } =>
SocketEvent::Disconnected { endpoint: clean(endpoint) },
SocketEvent::HandshakeFailed { endpoint, error_msg } =>
SocketEvent::HandshakeFailed { endpoint: clean(endpoint), error_msg },
SocketEvent::HandshakeSucceeded { endpoint } =>
SocketEvent::HandshakeSucceeded { endpoint: clean(endpoint) },
};
if tx.clone().to_sync().send(event).is_err() {
tracing::warn!(
socket_handle = self.handle,
"Failed to send event to monitor channel (full or closed)"
);
}
}
}
pub(crate) fn get_monitor_sender_clone(&self) -> Option<MonitorSender> {
self.monitor_tx.clone()
}
}
#[cfg(test)]
mod reconnect_tests {
use super::*;
use std::time::Duration;
#[test]
fn test_reconnect_backoff_doubling() {
let mut state = ReconnectState::default();
let base = Duration::from_millis(100);
let max = Duration::from_millis(1000);
let delay1 = state.on_connection_failure(base, max);
assert_eq!(delay1, Duration::from_millis(100));
let delay2 = state.on_connection_failure(base, max);
assert_eq!(delay2, Duration::from_millis(200));
let delay3 = state.on_connection_failure(base, max);
assert_eq!(delay3, Duration::from_millis(400));
}
#[test]
fn test_reconnect_backoff_ceiling() {
let mut state = ReconnectState::default();
let base = Duration::from_millis(100);
let max = Duration::from_millis(250);
let _ = state.on_connection_failure(base, max); let delay2 = state.on_connection_failure(base, max); let delay3 = state.on_connection_failure(base, max);
assert_eq!(delay2, Duration::from_millis(200));
assert_eq!(delay3, max);
}
#[test]
fn test_reconnect_overflow_prevention() {
let mut state = ReconnectState::default();
let base = Duration::from_millis(100);
let max = Duration::from_secs(60);
for _ in 0..100 {
let delay = state.on_connection_failure(base, max);
assert!(delay <= max, "Delay {:?} must not exceed max {:?}", delay, max);
}
assert_eq!(state.current_attempts, 100);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ShutdownPhase {
Running,
StoppingChildren,
Lingering,
CleaningPipes,
Finished,
}
#[derive(Debug)]
pub(crate) struct ShutdownCoordinator {
pub(crate) state: ShutdownPhase,
pub(crate) pending_child_actors: HashMap<usize, String>,
pub(crate) pending_connections_to_close: HashMap<usize, (String, Arc<dyn ISocketConnection>)>, #[cfg(feature = "inproc")]
pub(crate) inproc_connections_to_cleanup: Vec<(usize, usize, String)>, pub(crate) linger_deadline: Option<Instant>,
}
impl Default for ShutdownCoordinator {
fn default() -> Self {
Self {
state: ShutdownPhase::Running,
pending_child_actors: HashMap::new(),
pending_connections_to_close: HashMap::new(),
#[cfg(feature = "inproc")]
inproc_connections_to_cleanup: Vec::new(),
linger_deadline: None,
}
}
}