pub struct DialPool { /* private fields */ }Expand description
A bounded, permanent set of threads that own this role’s blocking TCP dials.
§Why the dial needs a thread at all
The connect is a blocking syscall and every caller is a task. On the exec
backend a task runs on a cooperative callback-band worker shared with every
other future on its band, so connecting inline parks the band for the whole
attempt — measured exactly there (gdb all-thread dump, host-linux
realtime-pva-ioc): one unanswering name server starved every future on Medium
for ~40 s per attempt. So the connect goes to a thread and the caller parks
on a oneshot instead.
§Why the threads are permanent
The obvious shape — one transient thread per dial — is unbounded in thread
creations, and creations are what cost on RTEMS (see MAX_DIAL_WORKERS).
A search engine whose name server is down redials roughly every 10 s for as
long as the IOC runs, so “transient, one per attempt” is a leak with no
ceiling. Making the workers permanent and reusing them removes the family
rather than capping it: after the first dial of each concurrency level there
is nothing left to create.
§What a worker owes the socket it opens
A worker is the single finalizer for every socket it opens. If the
caller gave up (timed out, or its future was dropped) the oneshot send fails
and the returned TcpStream is dropped right there, closing the fresh
socket. A worker that reaches a request whose caller is already gone skips
the connect entirely, so a backlog built up behind a blackholed peer costs
no sockets at all.
§Where the timeout is not
The worker issues a plain blocking TcpStream::connect — the CA client’s
proven on-target dial, C parity with tcpiiu.cpp’s blocking ::connect(),
and a thread that owns its blocking needs no poll machinery. The
application-level bound belongs to the awaiting side, which holds the
oneshot::Receiver this returns and is free to wrap it in
runtime::task::timeout. Do not add a bound here: the two are deliberately
split, and collapsing them puts the application deadline back inside a
syscall that cannot honour it.
Implementations§
Source§impl DialPool
impl DialPool
Sourcepub const fn new(name_prefix: &'static str, priority: ThreadPriority) -> Self
pub const fn new(name_prefix: &'static str, priority: ThreadPriority) -> Self
Declare a role’s dial pool. const so it can be a static: a pool is
per-role and lives as long as the process, so a caller needs no Arc
and no lazy initialiser.
Sourcepub fn worker_count(&self) -> usize
pub fn worker_count(&self) -> usize
Threads this pool has created — never more than MAX_DIAL_WORKERS.
The bound made observable: this is the number the per-attempt shape grew without limit.
Sourcepub fn queue_depth(&self) -> (usize, usize)
pub fn queue_depth(&self) -> (usize, usize)
Requests waiting for a worker, and workers currently inside a dial.
The other half of the bound: worker_count() alone cannot distinguish
“four workers, nothing queued” from “four workers, every one pinned and
a fifth dial waiting” — which is the state
MAX_DIAL_WORKERS exists to produce and the
only state in which the queueing it documents is observable.
Sourcepub fn dial(
&'static self,
target: SocketAddr,
) -> Result<Receiver<Result<TcpStream>>>
pub fn dial( &'static self, target: SocketAddr, ) -> Result<Receiver<Result<TcpStream>>>
Submit a dial. The returned receiver resolves with whatever the worker’s
connect returned.
The error is a thread-creation failure, and only that: it is returned before the request is queued, so a caller that sees it knows no dial is pending on its behalf.