Runtime options

Crate hopf-core: process-wide Runtime knobs — worker reactors, blocking storage pool, per-connection TCP listen/dial parameters, and the optional TelemetryHook.

RuntimeConfig

Field Type Default Meaning
worker_threads usize 0 Reactor count. 0 → available_parallelism * 2 (min 2)
storage StorageConfig StorageConfig::default() Blocking pool
use hopf_core::{Runtime, RuntimeConfig};

let rt = Runtime::start(RuntimeConfig {
    worker_threads: 4,
    ..Default::default()
})?;

With telemetry: Runtime::start_with_telemetry(config, Some(hook)) or Composition::new_with_telemetry(config, Some(hook)).

StorageConfig

Field Type Default Meaning
threads usize max(available_parallelism, 4) Fixed storage worker count
queue_capacity usize 4096 Bounded queue; excess submissions reject

Fail-fast backpressure: StorageError::Rejected — never blocks a reactor thread waiting for a storage slot.

TcpListenerConfig

TCP listen birth path — peer of TcpConnectorConfig.

Field Type Default Meaning
addr SocketAddr (required) Bind address; port 0 = ephemeral
factory HandlerFactory (required) Fn() -> Box<dyn ProtocolHandler>
max_net_in usize DEFAULT_MAX_NET_IN (1 MiB) Close if inbound buffer exceeds
max_net_out usize DEFAULT_MAX_NET_OUT (4 MiB) Close if outbound buffer exceeds
idle_timeout Option<Duration> None Idle (no receive); partially wired
secure bool false TLS-from-accept when true
tls Option<SharedTlsAcceptor> None Acceptor for TLS-from-accept / STARTTLS
acl PeerAcl PeerAcl::open() Peer allow/deny CIDRs
rate_limit Option<AcceptRateLimit> None Accept token bucket

Constants: DEFAULT_BUFFER_SIZE = 8 KiB (read chunk / initial buffer).

Builders

Method Effect
TcpListenerConfig::new(addr, factory) Plaintext defaults
max_net_in(n) / max_net_out(n) Override buffer caps
idle_timeout(Option<Duration>) Set idle timer
with_tls(acceptor) Sets secure = true + acceptor
with_starttls_acceptor(acceptor) Acceptor without TLS-from-accept
with_acl(PeerAcl) Replace ACL
with_rate_limit(AcceptRateLimit) Accept rate limit
use hopf_core::{PeerAcl, TcpListenerConfig};
use hopf_core::acceptor_from_pem;
use std::path::Path;
use std::time::Duration;

let acceptor = acceptor_from_pem(Path::new("c.pem"), Path::new("k.pem"), &[b"h2", b"http/1.1"])?;
let cfg = TcpListenerConfig::new(addr, factory)
    .max_net_in(2 * 1024 * 1024)
    .idle_timeout(Some(Duration::from_secs(60)))
    .with_tls(acceptor)
    .with_acl(PeerAcl::open());

TcpConnectorConfig

TCP dial birth path — same buffer / idle / TLS knobs as listen.

Field Type Default Meaning
addr SocketAddr (required) Peer (Stage 0: already resolved)
factory HandlerFactory (required) Handler for this dial
max_net_in usize 1 MiB Inbound cap
max_net_out usize 4 MiB Outbound cap
idle_timeout Option<Duration> None Idle timer (partial)
secure bool false TLS-from-dial when true
tls Option<SharedTlsConnector> None Client connector
server_name Option<String> None SNI / cert name (defaults to addr display)

Builders

Method Effect
TcpConnectorConfig::new(addr, factory) Plaintext defaults
max_net_in / max_net_out / idle_timeout Same as listen
with_tls(connector, server_name) TLS-from-dial + SNI
server_name(name) Override SNI without changing connector

ACL and accept rate limit

Type Knobs Behaviour
PeerAcl allow: Vec<IpNet>, deny: Vec<IpNet> Deny wins; empty allow ⇒ allow-all
IpNet::parse("addr/prefix") IPv4 / IPv6 CIDR Used by ACL
AcceptRateLimit::new(per_source, window, global) u32, Duration, u32 Token bucket; 0 = unlimited on that axis

Applied on the accept path before the connection is handed to a worker.

Idle timeout

idle_timeout: Option<Duration> exists on both listen and dial configs and is carried into TcpConnParams, but nothing in the reactor / connection datapath reads it yet — it is accepted and stored, not enforced. Do not rely on it to close idle connections. Prefer application-level timers via Endpoint::schedule_timer until this lands.

Telemetry hook

use hopf_core::TelemetryHook;
use std::net::SocketAddr;

pub trait TelemetryHook: Send + Sync {
    fn on_accept(&self, peer: SocketAddr) { /* ... */ }
    fn on_dial(&self, peer: SocketAddr) {}
    fn on_close(&self, peer: SocketAddr) {}
    fn on_error(&self, peer: Option<SocketAddr>, msg: &str) {}
}
Type Role
TelemetryHook Off-hot-path callbacks (enqueue only)
NopTelemetry Discarding default
Runtime::start_with_telemetry Install at start
Composition::new_with_telemetry / from_xml*_with_telemetry Same, via Composition — must be supplied at start, since it can't attach after the Runtime is already running

Production exporters live in hopf-otel (TelemetryPipeline::hook()). See telemetry.md.

StorageExecutor

Method Purpose
submit(&dyn Endpoint, op, callback) Run op off-loop; hop result via endpoint
submit_on(ConnHandle, op, callback) Same with a cloned handle
pending_count() Approximate queued + running jobs
shutdown(self) Drain / stop pool

HTTP servers typically obtain ConnHandle from ServerWriter::conn_handle or ServerResponseHandle — see http/server.md.

Worker handles

API Purpose
rt.worker(i) Indexed ReactorHandle
rt.pick_worker() Round-robin worker
rt.worker_count() Reactor count
rt.schedule_on_worker(idx, delay, FnOnce) Timer on a chosen worker
rt.storage() &Arc<StorageExecutor>

Use worker handles for UDP registration (DNS), reactor-affine timers, and advanced scheduling — not for blocking filesystem work.

See also