Runtime options
Crate hopf-core: process-wide Runtime knobs — worker reactors, blocking storage pool, per-connection TCP listen/dial parameters, and the optional TelemetryHook.
Contents
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.