#![allow(clippy::disallowed_methods)]
use std::future::Future;
use std::sync::{Arc, OnceLock};
use conflaguration::{Validate, ValidationMessage};
use proxima_core::ProximaError;
use proxima_primitives::stream::{AcceptorFactory, DatagramFactory};
#[cfg(feature = "runtime-prime")]
pub use prime;
#[cfg(feature = "runtime-tokio")]
pub use proxima_runtime::tokio as tokio_per_core;
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
pub use prime::os::runtime::PrimeRuntime;
#[cfg(feature = "rayon")]
pub use proxima_runtime::RayonBackgroundPool;
#[cfg(feature = "runtime-tokio")]
pub use tokio_per_core::TokioPerCoreRuntime;
pub use proxima_runtime::*;
static INSTALLED_RUNTIME: OnceLock<RuntimeSelection> = OnceLock::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuntimeBackend {
Prime,
Tokio,
Other(&'static str),
}
impl RuntimeBackend {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
RuntimeBackend::Prime => "prime",
RuntimeBackend::Tokio => "tokio",
RuntimeBackend::Other(name) => name,
}
}
}
#[derive(Clone)]
pub struct RuntimeSelection {
pub backend: RuntimeBackend,
pub runtime: Arc<dyn Runtime>,
pub acceptor_factory: Arc<dyn AcceptorFactory>,
pub datagram_factory: Option<Arc<dyn DatagramFactory>>,
pub unix_upstream_factory: Option<Arc<dyn proxima_primitives::stream::UnixUpstreamFactory>>,
pub packet_listener_factory: Option<Arc<dyn proxima_net::packet::PacketListenerFactory>>,
}
impl Validate for RuntimeSelection {
fn validate(&self) -> conflaguration::Result<()> {
let Some(factory) = &self.datagram_factory else {
return Ok(());
};
let factory_backend = factory.backend_name();
let selection_backend = self.backend.name();
if factory_backend == "unknown" || factory_backend == selection_backend {
return Ok(());
}
Err(conflaguration::Error::Validation {
errors: vec![ValidationMessage::new(
"datagram_factory",
format!(
"{factory_backend} datagram factory requires a {factory_backend} runtime; \
got {selection_backend}"
),
)],
})
}
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
impl RuntimeSelection {
#[must_use]
pub fn from_prime(runtime: Arc<dyn Runtime>) -> Self {
Self {
backend: RuntimeBackend::Prime,
runtime,
acceptor_factory: Arc::new(proxima_net::prime::PrimeAcceptorFactory),
datagram_factory: Some(Arc::new(proxima_net::prime::PrimeDatagramFactory)),
#[cfg(unix)]
unix_upstream_factory: Some(Arc::new(proxima_net::prime::PrimeUnixUpstreamFactory)),
#[cfg(not(unix))]
unix_upstream_factory: None,
packet_listener_factory: Some(Arc::new(proxima_net::prime::PrimePacketListenerFactory)),
}
}
pub fn prime(cores: usize) -> Result<Self, ProximaError> {
Ok(Self::from_prime(Arc::new(PrimeRuntime::new(cores)?)))
}
}
#[cfg(feature = "runtime-tokio")]
impl RuntimeSelection {
#[must_use]
pub fn from_tokio(runtime: Arc<dyn Runtime>) -> Self {
Self {
backend: RuntimeBackend::Tokio,
runtime,
acceptor_factory: Arc::new(proxima_net::tokio::TokioAcceptorFactory),
datagram_factory: Some(Arc::new(proxima_net::tokio::TokioDatagramFactory)),
#[cfg(unix)]
unix_upstream_factory: Some(Arc::new(proxima_net::tokio::TokioUnixUpstreamFactory)),
#[cfg(not(unix))]
unix_upstream_factory: None,
packet_listener_factory: Some(Arc::new(proxima_net::tokio::TokioPacketListenerFactory)),
}
}
pub fn tokio(cores: usize) -> Result<Self, ProximaError> {
Ok(Self::from_tokio(Arc::new(TokioPerCoreRuntime::new(cores)?)))
}
}
pub fn install_runtime(selection: RuntimeSelection) {
let _ = INSTALLED_RUNTIME.set(selection);
}
#[must_use]
pub fn installed_runtime() -> Option<RuntimeSelection> {
INSTALLED_RUNTIME.get().cloned()
}
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
use std::net::SocketAddr;
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
use proxima_http::listener::HttpListenProtocol;
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
use proxima_listen::handle::ListenerHandle;
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
use proxima_listen::{ListenRegistry, ListenerSpec};
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
use proxima_primitives::pipe::handler::PipeHandle;
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
use proxima_primitives::pipe::telemetry_surface::NoopTelemetry;
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
pub trait PrimeServeExt {
fn serve_http(
self: &Arc<Self>,
bind: SocketAddr,
pipe: PipeHandle,
) -> Result<ListenerHandle, ProximaError>;
#[cfg(feature = "tls")]
fn serve_https(
self: &Arc<Self>,
bind: SocketAddr,
cert: std::path::PathBuf,
key: std::path::PathBuf,
pipe: PipeHandle,
) -> Result<ListenerHandle, ProximaError>;
#[cfg(feature = "tls")]
fn serve_https_with_tls(
self: &Arc<Self>,
bind: SocketAddr,
tls: proxima_tls::TlsConfig,
pipe: PipeHandle,
) -> Result<ListenerHandle, ProximaError>;
}
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
fn make_http_registry() -> Result<ListenRegistry, ProximaError> {
let registry = ListenRegistry::new();
registry.register(Arc::new(HttpListenProtocol::new()))?;
Ok(registry)
}
#[cfg(all(
any(feature = "http1", feature = "http1-native"),
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
impl PrimeServeExt for PrimeRuntime {
fn serve_http(
self: &Arc<Self>,
bind: SocketAddr,
pipe: PipeHandle,
) -> Result<ListenerHandle, ProximaError> {
let registry = make_http_registry()?;
let runtime: Arc<dyn proxima_runtime::Runtime> = self.clone();
let acceptor = Arc::new(proxima_net::prime::PrimeAcceptorFactory);
ListenerSpec::http(bind).attach(pipe).run_with_runtime(
®istry,
NoopTelemetry::handle(),
Some(runtime),
Some(acceptor),
None,
)
}
#[cfg(feature = "tls")]
fn serve_https(
self: &Arc<Self>,
bind: SocketAddr,
cert: std::path::PathBuf,
key: std::path::PathBuf,
pipe: PipeHandle,
) -> Result<ListenerHandle, ProximaError> {
let tls = proxima_tls::TlsConfig::files(cert, key)?;
self.serve_https_with_tls(bind, tls, pipe)
}
#[cfg(feature = "tls")]
fn serve_https_with_tls(
self: &Arc<Self>,
bind: SocketAddr,
tls: proxima_tls::TlsConfig,
pipe: PipeHandle,
) -> Result<ListenerHandle, ProximaError> {
let registry = ListenRegistry::new();
let runtime: Arc<dyn proxima_runtime::Runtime> = self.clone();
let acceptor = Arc::new(proxima_net::prime::PrimeAcceptorFactory);
let protocol: Arc<dyn proxima_listen::ListenProtocol> = Arc::new(
proxima_listen::TlsListenProtocol::new(Arc::new(HttpListenProtocol::new()), tls),
);
ListenerSpec::protocol(bind, protocol)
.attach(pipe)
.run_with_runtime(
®istry,
NoopTelemetry::handle(),
Some(runtime),
Some(acceptor),
None,
)
}
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
pub fn run_prime<F>(future: F) -> Result<F::Output, ProximaError>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
run_prime_with_cores(None, None, future)
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
pub fn run_prime_with_cores<F>(
cores: Option<usize>,
affinity: Option<&str>,
future: F,
) -> Result<F::Output, ProximaError>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let (visible_cores, placement_affinity) = resolve_visible_cores(cores, affinity)?;
let driver_core = CoreId(visible_cores);
let mut placement = placement_affinity.placement(visible_cores);
let driver_physical_core = placement.last().copied().unwrap_or(visible_cores);
placement.push(driver_physical_core);
let pin = !matches!(placement_affinity, prime::config::Affinity::Float);
let inner = Arc::new(build_prime_run_runtime(placement, pin)?);
install_prime_ambient(&inner, visible_cores);
let (sender, receiver) = std::sync::mpsc::sync_channel::<F::Output>(1);
let task = async move {
let output = future.await;
let _ = sender.send(output);
};
match inner.spawn_on_core(driver_core, Box::pin(task)) {
Ok(()) => {}
Err(SpawnError::InboxFull) => {
return Err(prime_run_dispatch_error(
"prime driver core inbox full on run_prime dispatch",
));
}
Err(SpawnError::Disconnected) => {
return Err(prime_run_dispatch_error(
"prime driver core disconnected on run_prime dispatch",
));
}
}
receiver.recv().map_err(|_| {
prime_run_dispatch_error("prime worker dropped the run_prime completion channel")
})
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
fn resolve_visible_cores(
cores: Option<usize>,
affinity: Option<&str>,
) -> Result<(usize, prime::config::Affinity), ProximaError> {
let placement_affinity = match affinity {
Some(spec) => {
<prime::config::Affinity as std::str::FromStr>::from_str(spec).map_err(|error| {
prime_run_dispatch_error(&format!("invalid affinity `{spec}`: {error}"))
})?
}
None => prime::config::Affinity::Float,
};
let visible_cores = match (cores, placement_affinity.fixed_count()) {
(Some(cores_count), Some(affinity_count)) if cores_count != affinity_count => {
return Err(prime_run_dispatch_error(&format!(
"cores = {cores_count} conflicts with affinity {affinity:?}, which names {affinity_count} cores"
)));
}
(_, Some(affinity_count)) => affinity_count,
(Some(cores_count), None) => cores_count,
(None, None) => prime::config::CoreSelection::Auto.resolve(),
}
.max(1);
Ok((visible_cores, placement_affinity))
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
fn prime_run_dispatch_error(message: &str) -> ProximaError {
ProximaError::Io(std::io::Error::other(message))
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
fn build_prime_run_runtime(placement: Vec<usize>, pin: bool) -> Result<PrimeRuntime, ProximaError> {
#[cfg(feature = "run-prime-tokio-compat")]
{
PrimeRuntime::new_inner_placed(placement, pin, true)
}
#[cfg(not(feature = "run-prime-tokio-compat"))]
{
PrimeRuntime::new_inner_placed(placement, pin, false)
}
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
struct AdoptedRuntime {
inner: Arc<PrimeRuntime>,
visible_cores: usize,
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
impl Runtime for AdoptedRuntime {
fn spawn_on_current_core(&self, future: std::pin::Pin<Box<dyn Future<Output = ()> + 'static>>) {
self.inner.spawn_on_current_core(future);
}
fn spawn_on_core(
&self,
core_id: CoreId,
future: std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
) -> Result<(), SpawnError> {
self.inner.spawn_on_core(core_id, future)
}
fn spawn_factory_on_core(
&self,
core_id: CoreId,
factory: Box<
dyn FnOnce() -> std::pin::Pin<Box<dyn Future<Output = ()> + 'static>> + Send + 'static,
>,
) -> Result<(), SpawnError> {
self.inner.spawn_factory_on_core(core_id, factory)
}
fn spawn_background_blocking(
&self,
work: Box<dyn FnOnce() -> Result<Box<dyn std::any::Any + Send>, ProximaError> + Send>,
) -> BackgroundHandle<Box<dyn std::any::Any + Send>> {
self.inner.spawn_background_blocking(work)
}
fn timer_at(
&self,
deadline: std::time::Instant,
) -> std::pin::Pin<Box<dyn Future<Output = ()> + 'static>> {
self.inner.timer_at(deadline)
}
fn num_cores(&self) -> usize {
self.visible_cores
}
fn current_core(&self) -> CoreId {
self.inner.current_core()
}
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool",
feature = "serve-prime",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
fn install_prime_ambient(inner: &Arc<PrimeRuntime>, visible_cores: usize) {
let runtime: Arc<dyn Runtime> = Arc::new(AdoptedRuntime {
inner: inner.clone(),
visible_cores,
});
install_runtime(RuntimeSelection::from_prime(runtime));
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool",
not(all(
feature = "serve-prime",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))
))]
fn install_prime_ambient(_inner: &Arc<PrimeRuntime>, _visible_cores: usize) {}
#[cfg(feature = "tokio")]
pub fn run_tokio<F>(
multi_thread: bool,
worker_threads: Option<usize>,
future: F,
) -> Result<F::Output, ProximaError>
where
F: Future,
{
let mut builder = if multi_thread {
let mut builder = ::tokio::runtime::Builder::new_multi_thread();
if let Some(count) = worker_threads {
builder.worker_threads(count);
}
builder
} else {
::tokio::runtime::Builder::new_current_thread()
};
builder.enable_all();
let runtime = builder.build().map_err(|err| {
ProximaError::Io(std::io::Error::other(format!("build tokio runtime: {err}")))
})?;
Ok(runtime.block_on(future))
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
pub fn run<F>(future: F) -> Result<F::Output, ProximaError>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
run_prime(future)
}
#[cfg(all(
not(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
)),
feature = "tokio"
))]
pub fn run<F>(future: F) -> Result<F::Output, ProximaError>
where
F: Future,
{
run_tokio(true, None, future)
}
#[cfg(not(any(
all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
),
feature = "tokio"
)))]
pub fn run<F>(_future: F) -> Result<F::Output, ProximaError>
where
F: Future,
{
Err(ProximaError::Config(
"no runtime backend compiled in: enable the prime bundle (serve-prime) or `tokio`".into(),
))
}
#[cfg(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
pub fn run_with_cores<F>(
cores: Option<usize>,
affinity: Option<&str>,
future: F,
) -> Result<F::Output, ProximaError>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
run_prime_with_cores(cores, affinity, future)
}
#[cfg(all(
not(all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
)),
feature = "tokio"
))]
pub fn run_with_cores<F>(
cores: Option<usize>,
affinity: Option<&str>,
future: F,
) -> Result<F::Output, ProximaError>
where
F: Future,
{
let _ = affinity;
run_tokio(true, cores, future)
}
#[cfg(not(any(
all(
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
),
feature = "tokio"
)))]
pub fn run_with_cores<F>(
_cores: Option<usize>,
_affinity: Option<&str>,
_future: F,
) -> Result<F::Output, ProximaError>
where
F: Future,
{
Err(ProximaError::Config(
"no runtime backend compiled in: enable the prime bundle (serve-prime) or `tokio`".into(),
))
}
#[cfg(all(
test,
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool"
))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn bare_cores_and_affinity_resolve_to_all_physical_cores() {
let expected = prime::config::CoreSelection::Auto.resolve();
let (observed, _) = resolve_visible_cores(None, None).expect("bare cores resolve");
assert_eq!(observed, expected);
}
#[test]
fn explicit_cores_overrides_the_auto_default() {
let (observed, _) = resolve_visible_cores(Some(2), None).expect("explicit cores resolve");
assert_eq!(observed, 2);
}
#[test]
fn affinity_cores_list_fixes_the_count_without_an_explicit_cores_arg() {
let (observed, _) =
resolve_visible_cores(None, Some("4,5,6")).expect("affinity list resolves");
assert_eq!(observed, 3);
}
#[test]
fn run_with_cores_returns_the_future_output_from_the_booted_runtime() {
let observed = run_with_cores(Some(2), None, async { 7_usize }).expect("run_with_cores");
assert_eq!(observed, 7);
}
#[test]
fn cores_conflicting_with_affinity_list_length_is_an_error() {
let result = run_with_cores(Some(2), Some("4,5,6"), async {});
assert!(
result.is_err(),
"cores = 2 must not silently win over a 3-entry affinity list"
);
}
#[test]
fn invalid_affinity_literal_is_an_error_not_a_panic() {
let result = run_with_cores(None, Some("not-a-valid-spec"), async {});
assert!(result.is_err());
}
}
#[cfg(all(
test,
feature = "runtime-prime-executor",
feature = "runtime-prime-inbox-alloc",
feature = "runtime-prime-reactor",
feature = "runtime-prime-bgpool",
feature = "runtime-tokio",
feature = "tokio",
any(target_os = "linux", target_os = "macos", target_os = "windows")
))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod selection_by_value_tests {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
struct RecordingRuntime {
ran: Arc<AtomicBool>,
}
impl Runtime for RecordingRuntime {
fn spawn_on_current_core(
&self,
future: std::pin::Pin<Box<dyn Future<Output = ()> + 'static>>,
) {
futures::executor::block_on(future);
self.ran.store(true, Ordering::SeqCst);
}
fn spawn_on_core(
&self,
_core_id: CoreId,
_future: std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
) -> Result<(), SpawnError> {
unreachable!("test never routes to a peer core")
}
fn spawn_factory_on_core(
&self,
_core_id: CoreId,
_factory: Box<
dyn FnOnce() -> std::pin::Pin<Box<dyn Future<Output = ()> + 'static>>
+ Send
+ 'static,
>,
) -> Result<(), SpawnError> {
unreachable!("test never spawns a cross-core factory")
}
fn spawn_background_blocking(
&self,
_work: Box<dyn FnOnce() -> Result<Box<dyn std::any::Any + Send>, ProximaError> + Send>,
) -> BackgroundHandle<Box<dyn std::any::Any + Send>> {
unreachable!("test never spawns background-blocking work")
}
fn timer_at(
&self,
_deadline: std::time::Instant,
) -> std::pin::Pin<Box<dyn Future<Output = ()> + 'static>> {
unreachable!("test never times out")
}
fn num_cores(&self) -> usize {
1
}
fn current_core(&self) -> CoreId {
CoreId(0)
}
}
#[test]
fn app_runtime_follows_the_injected_selection_value_not_feature_presence() {
let flag_prime = Arc::new(AtomicBool::new(false));
let flag_tokio = Arc::new(AtomicBool::new(false));
let prime_runtime: Arc<dyn Runtime> = Arc::new(RecordingRuntime {
ran: flag_prime.clone(),
});
let tokio_runtime: Arc<dyn Runtime> = Arc::new(RecordingRuntime {
ran: flag_tokio.clone(),
});
let prime_selection = RuntimeSelection::from_prime(prime_runtime);
let tokio_selection = RuntimeSelection::from_tokio(tokio_runtime);
assert_eq!(prime_selection.backend, RuntimeBackend::Prime);
assert_eq!(tokio_selection.backend, RuntimeBackend::Tokio);
assert!(
prime_selection.datagram_factory.is_some(),
"prime bundles a DatagramFactory"
);
assert!(
tokio_selection.datagram_factory.is_some(),
"tokio bundles a DatagramFactory too (TokioDatagramFactory)"
);
prime_selection
.validate()
.expect("from_prime always validates");
tokio_selection
.validate()
.expect("from_tokio always validates");
let app_prime = crate::App::builder()
.runtime(prime_selection)
.with_defaults()
.expect("with_defaults")
.build()
.expect("build prime app");
let app_tokio = crate::App::builder()
.runtime(tokio_selection)
.with_defaults()
.expect("with_defaults")
.build()
.expect("build tokio app");
proxima_listen::dispatch_handler(
app_prime.runtime().as_ref(),
proxima_listen::Route::Inline,
Box::pin(async {}),
);
assert!(
flag_prime.load(Ordering::SeqCst),
"app built from RuntimeSelection::from_prime must dispatch on the prime runtime"
);
assert!(
!flag_tokio.load(Ordering::SeqCst),
"the tokio app's runtime must be untouched by the prime app's dispatch"
);
proxima_listen::dispatch_handler(
app_tokio.runtime().as_ref(),
proxima_listen::Route::Inline,
Box::pin(async {}),
);
assert!(
flag_tokio.load(Ordering::SeqCst),
"app built from RuntimeSelection::from_tokio must dispatch on the tokio runtime"
);
}
#[test]
fn foreign_backend_can_be_constructed_and_driven_through_runtime_selection() {
let flag_foreign = Arc::new(AtomicBool::new(false));
let foreign_runtime: Arc<dyn Runtime> = Arc::new(RecordingRuntime {
ran: flag_foreign.clone(),
});
let foreign_selection = RuntimeSelection {
backend: RuntimeBackend::Other("smol"),
runtime: foreign_runtime,
acceptor_factory: Arc::new(proxima_net::prime::PrimeAcceptorFactory),
datagram_factory: None,
unix_upstream_factory: None,
packet_listener_factory: None,
};
assert_eq!(foreign_selection.backend, RuntimeBackend::Other("smol"));
assert_ne!(foreign_selection.backend, RuntimeBackend::Prime);
assert_ne!(foreign_selection.backend, RuntimeBackend::Tokio);
let app_foreign = crate::App::builder()
.runtime(foreign_selection)
.with_defaults()
.expect("with_defaults")
.build()
.expect("build foreign-backend app");
proxima_listen::dispatch_handler(
app_foreign.runtime().as_ref(),
proxima_listen::Route::Inline,
Box::pin(async {}),
);
assert!(
flag_foreign.load(Ordering::SeqCst),
"app built from a RuntimeSelection with RuntimeBackend::Other must dispatch on the foreign runtime"
);
}
#[test]
fn validate_rejects_a_prime_datagram_factory_on_a_tokio_selection() {
let flag = Arc::new(AtomicBool::new(false));
let tokio_runtime: Arc<dyn Runtime> = Arc::new(RecordingRuntime { ran: flag });
let mismatched = RuntimeSelection {
backend: RuntimeBackend::Tokio,
runtime: tokio_runtime,
acceptor_factory: Arc::new(proxima_net::tokio::TokioAcceptorFactory),
datagram_factory: Some(Arc::new(proxima_net::prime::PrimeDatagramFactory)),
unix_upstream_factory: None,
packet_listener_factory: None,
};
let error = mismatched
.validate()
.expect_err("a prime datagram factory on a tokio selection must fail validate()");
let message = error.to_string();
assert!(
message.contains("prime datagram factory requires a prime runtime"),
"error must name which backend the factory requires: {message}"
);
assert!(
message.contains("got tokio"),
"error must name the actual backend it got: {message}"
);
}
#[test]
fn validate_rejects_a_tokio_datagram_factory_on_a_prime_selection() {
let flag = Arc::new(AtomicBool::new(false));
let prime_runtime: Arc<dyn Runtime> = Arc::new(RecordingRuntime { ran: flag });
let mismatched = RuntimeSelection {
backend: RuntimeBackend::Prime,
runtime: prime_runtime,
acceptor_factory: Arc::new(proxima_net::prime::PrimeAcceptorFactory),
datagram_factory: Some(Arc::new(proxima_net::tokio::TokioDatagramFactory)),
unix_upstream_factory: None,
packet_listener_factory: None,
};
let error = mismatched
.validate()
.expect_err("a tokio datagram factory on a prime selection must fail validate()");
let message = error.to_string();
assert!(
message.contains("tokio datagram factory requires a tokio runtime"),
"error must name which backend the factory requires: {message}"
);
assert!(
message.contains("got prime"),
"error must name the actual backend it got: {message}"
);
}
#[test]
fn validate_never_flags_a_factory_with_no_declared_backend() {
struct FakeFactory;
impl DatagramFactory for FakeFactory {
fn bind(
&self,
_addr: std::net::SocketAddr,
) -> std::io::Result<Box<dyn proxima_primitives::stream::DatagramSocket>> {
Err(std::io::Error::other("fake factory never actually binds"))
}
}
let flag = Arc::new(AtomicBool::new(false));
let tokio_runtime: Arc<dyn Runtime> = Arc::new(RecordingRuntime { ran: flag });
let selection = RuntimeSelection {
backend: RuntimeBackend::Tokio,
runtime: tokio_runtime,
acceptor_factory: Arc::new(proxima_net::tokio::TokioAcceptorFactory),
datagram_factory: Some(Arc::new(FakeFactory)),
unix_upstream_factory: None,
packet_listener_factory: None,
};
selection
.validate()
.expect("an unlabeled datagram factory must never be flagged as a mismatch");
}
}