Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use service_info::{EventGroupInfo, ServiceInfo};
pub use subscription_manager::{StaticSubscriptionHandle, StaticSubscriptionStorage};
pub use subscription_manager::{SubscribeError, SubscriptionHandle, SubscriptionManager};

use sd_state::SdStateManager;
pub use sd_state::{SdStateHandle, SdStateManager, WrappableSdStateHandle};

use core::sync::atomic::{AtomicBool, Ordering};

Expand Down Expand Up @@ -143,14 +143,15 @@ where
/// these as `Arc<Mutex<E2ERegistry>>` / `Arc<RwLock<SubscriptionManager>>`
/// / `TokioTransport` / `TokioTimer`. Bare-metal callers use
/// [`Self::new_with_deps`] (under `server`) and supply their own.
pub struct Server<R, S, F, Tm, H = Arc<<F as TransportFactory>::Socket>>
pub struct Server<R, S, F, Tm, H = Arc<<F as TransportFactory>::Socket>, Hsd = Arc<SdStateManager>>
where
R: E2ERegistryHandle,
S: SubscriptionHandle,
F: TransportFactory + 'static,
F::Socket: 'static,
Tm: Timer + Clone + 'static,
H: SocketHandle<Socket = F::Socket>,
Hsd: SdStateHandle,
{
config: ServerConfig,
/// Socket for receiving subscription requests, behind whatever
Expand All @@ -164,8 +165,10 @@ where
subscriptions: S,
/// Event publisher
publisher: Arc<EventPublisher<R, S, H>>,
/// SD session-ID counter and announcement emitter
sd_state: Arc<SdStateManager>,
/// SD session-ID counter and announcement emitter, behind whatever
/// shared-storage `Hsd` chose (`Arc<SdStateManager>` on std,
/// `&'static SdStateManager` on bare-metal-no-alloc).
sd_state: Hsd,
/// Shared E2E registry for runtime E2E configuration
e2e_registry: R,
/// Transport factory. Used at construction time to bind sockets;
Expand Down Expand Up @@ -279,14 +282,15 @@ impl
}
}

impl<R, S, F, Tm, H> Server<R, S, F, Tm, H>
impl<R, S, F, Tm, H, Hsd> Server<R, S, F, Tm, H, Hsd>
where
R: E2ERegistryHandle,
S: SubscriptionHandle,
F: TransportFactory + 'static,
F::Socket: 'static,
Tm: Timer + Clone + 'static,
H: WrappableSocketHandle<Socket = F::Socket>,
Hsd: WrappableSdStateHandle,
{
/// Bare-metal-friendly constructor that takes every dependency
/// explicitly via a [`ServerDeps`] bundle. The `server-tokio`
Expand Down Expand Up @@ -366,7 +370,7 @@ where
sd_socket,
subscriptions,
publisher,
sd_state: Arc::new(SdStateManager::new()),
sd_state: Hsd::wrap(SdStateManager::new()),
e2e_registry,
factory,
timer,
Expand Down Expand Up @@ -436,7 +440,7 @@ where
sd_socket,
subscriptions,
publisher,
sd_state: Arc::new(SdStateManager::new()),
sd_state: Hsd::wrap(SdStateManager::new()),
e2e_registry,
factory,
timer,
Expand All @@ -446,14 +450,15 @@ where
}
}

impl<R, S, F, Tm, H> Server<R, S, F, Tm, H>
impl<R, S, F, Tm, H, Hsd> Server<R, S, F, Tm, H, Hsd>
where
R: E2ERegistryHandle,
S: SubscriptionHandle,
F: TransportFactory + 'static,
F::Socket: 'static,
Tm: Timer + Clone + 'static,
H: SocketHandle<Socket = F::Socket>,
Hsd: SdStateHandle,
{
/// Build the periodic-SD-announcement future.
///
Expand Down Expand Up @@ -496,6 +501,7 @@ where
F::Socket: Send + Sync,
for<'a> <F::Socket as TransportSocket>::SendFuture<'a>: Send,
H: Send + Sync,
Hsd: Send + Sync,
Tm: Send + Sync,
for<'a> Tm::SleepFuture<'a>: Send,
{
Expand Down Expand Up @@ -523,13 +529,14 @@ where
}
let config = self.config.clone();
let sd_socket = self.sd_socket.clone();
let sd_state = Arc::clone(&self.sd_state);
let sd_state = self.sd_state.clone();
let timer = self.timer.clone();

Ok(async move {
let mut announcement_count = 0u32;
loop {
match sd_state
.sd_state()
.send_offer_service(&config, sd_socket.socket())
.await
{
Expand Down Expand Up @@ -602,13 +609,14 @@ where
}
let config = self.config.clone();
let sd_socket = self.sd_socket.clone();
let sd_state = Arc::clone(&self.sd_state);
let sd_state = self.sd_state.clone();
let timer = self.timer.clone();

Ok(async move {
let mut announcement_count = 0u32;
loop {
match sd_state
.sd_state()
.send_offer_service(&config, sd_socket.socket())
.await
{
Expand Down Expand Up @@ -663,7 +671,7 @@ where
// Atomic (sid, reboot_flag) pair so concurrent emissions cannot
// race around the wrap boundary — see
// `SdStateManager::next_session_id_with_reboot_flag` docs.
let (sid, reboot_flag) = self.sd_state.next_session_id_with_reboot_flag();
let (sid, reboot_flag) = self.sd_state.sd_state().next_session_id_with_reboot_flag();
let sd_payload = sd::Header::new(Flags::new_sd(reboot_flag), &entries, &options);

let mut buffer = [0u8; crate::UDP_BUFFER_SIZE];
Expand Down Expand Up @@ -1211,14 +1219,15 @@ fn extract_subscriber_endpoint(
}
}

impl<R, S, F, Tm, H> Server<R, S, F, Tm, H>
impl<R, S, F, Tm, H, Hsd> Server<R, S, F, Tm, H, Hsd>
where
R: E2ERegistryHandle,
S: SubscriptionHandle,
F: TransportFactory + 'static,
F::Socket: 'static,
Tm: Timer + Clone + 'static,
H: SocketHandle<Socket = F::Socket>,
Hsd: SdStateHandle,
{
/// Send `SubscribeAck` from an entry view
async fn send_subscribe_ack_from_view(
Expand All @@ -1244,7 +1253,7 @@ where
let entries = [ack_entry];
// Atomic (sid, reboot_flag) pair — see
// `SdStateManager::next_session_id_with_reboot_flag`.
let (sid, reboot_flag) = self.sd_state.next_session_id_with_reboot_flag();
let (sid, reboot_flag) = self.sd_state.sd_state().next_session_id_with_reboot_flag();
let sd_payload = sd::Header::new(Flags::new_sd(reboot_flag), &entries, &[]);

let mut buffer = [0u8; crate::UDP_BUFFER_SIZE];
Expand Down Expand Up @@ -1294,7 +1303,7 @@ where
let entries = [nack_entry];
// Atomic (sid, reboot_flag) pair — see
// `SdStateManager::next_session_id_with_reboot_flag`.
let (sid, reboot_flag) = self.sd_state.next_session_id_with_reboot_flag();
let (sid, reboot_flag) = self.sd_state.sd_state().next_session_id_with_reboot_flag();
let sd_payload = sd::Header::new(Flags::new_sd(reboot_flag), &entries, &[]);

let mut buffer = [0u8; crate::UDP_BUFFER_SIZE];
Expand Down
91 changes: 89 additions & 2 deletions src/server/sd_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use super::{Error, ServerConfig};
/// tracks that transition and exposes it via [`Self::reboot_flag`] so every
/// server-side SD emission path reads from a single source of truth.
#[derive(Debug)]
pub(super) struct SdStateManager {
pub struct SdStateManager {
/// Packed `(has_wrapped, session_id)` state.
///
/// - bits 0..16: current session id (1..=0xFFFF, never 0).
Expand All @@ -50,7 +50,19 @@ const SID_MASK: u32 = 0xFFFF;
const WRAPPED_BIT: u32 = 1 << 16;

impl SdStateManager {
pub(super) const fn new() -> Self {
/// Construct an `SdStateManager` with a fresh session counter
/// (starts at `1`, reboot flag = `RecentlyRebooted`).
///
/// `const fn` so consumers can declare a `static`-storage instance
/// without an allocator:
///
/// ```ignore
/// static SD_STATE: SdStateManager = SdStateManager::new();
/// // pass `&SD_STATE` (an `&'static SdStateManager`) into the
/// // appropriate `Server` constructor.
/// ```
#[must_use]
pub const fn new() -> Self {
Self::with_initial(1)
}

Expand Down Expand Up @@ -204,6 +216,81 @@ impl SdStateManager {
}
}

/// Shared handle to the [`SdStateManager`] backing a [`Server`].
///
/// Abstracts how the SD-session-state is shared between the Server's
/// run loop and its spawned `announcement_loop` future. Two impls
/// ship out of the box, mirroring the pattern established by
/// [`crate::transport::SocketHandle`]:
///
/// - `Arc<SdStateManager>` on alloc-using builds — the existing
/// default for `Server::new_with_deps`.
/// - `&'static SdStateManager` on bare-metal-no-alloc — caller
/// declares a `static SdStateManager = SdStateManager::new();`
/// and passes the reference into a future
/// `Server::new_with_handles` constructor.
///
/// Required to be `Clone + 'static` so the handle can be cheaply
/// cloned into the announcement-loop future without borrowing
/// `&self`. The bound is intentionally permissive — neither `Send`
/// nor `Sync` at the trait level — so a `!Send` storage backend
/// (e.g., `Rc<SdStateManager>` if a single-threaded alloc target
/// ever wants it) would also satisfy.
///
/// [`Server`]: crate::server::Server
pub trait SdStateHandle: Clone + 'static {
/// Borrow the underlying `SdStateManager` for SD-session-state
/// reads / atomic increments.
fn sd_state(&self) -> &SdStateManager;
}

// `&'static SdStateManager` is the no-alloc handle. `&'static T` is
// `Copy + Clone + 'static` for any `T: 'static` so the trait bounds
// are met without further work — the user only needs to declare
// the underlying `static` storage once at boot.
impl SdStateHandle for &'static SdStateManager {
fn sd_state(&self) -> &SdStateManager {
self
}
}

#[cfg(any(feature = "embassy_channels", feature = "server"))]
impl SdStateHandle for alloc::sync::Arc<SdStateManager> {
fn sd_state(&self) -> &SdStateManager {
self
}
}

/// Extension of [`SdStateHandle`] for handles that can be
/// constructed inline from an owned `SdStateManager`.
///
/// Required by `Server` constructors that build an `SdStateManager`
/// internally (the alloc-using path —
/// `Server::new_with_deps` calls `SdStateManager::new()` then wraps).
/// The future `Server::new_with_handles` (post-alloc-audit follow-up)
/// will accept a pre-built `Hsd: SdStateHandle` directly and won't
/// need this trait.
///
/// `&'static SdStateManager` deliberately does **not** implement this
/// trait — there is no allocator-free way to materialize a `&'static`
/// reference inside a trait method (the user has to declare a
/// `static` themselves and supply the reference via a different
/// constructor). This mirrors how
/// [`crate::transport::WrappableSocketHandle`] is split from
/// [`crate::transport::SocketHandle`].
pub trait WrappableSdStateHandle: SdStateHandle {
/// Place an owned `SdStateManager` behind this handle's shared
/// storage.
fn wrap(state: SdStateManager) -> Self;
}

#[cfg(any(feature = "embassy_channels", feature = "server"))]
impl WrappableSdStateHandle for alloc::sync::Arc<SdStateManager> {
fn wrap(state: SdStateManager) -> Self {
alloc::sync::Arc::new(state)
}
}

#[cfg(all(test, feature = "server-tokio"))]
mod tests {
use super::{SdStateManager, ServerConfig};
Expand Down