pub struct DispatcherConfig {Show 16 fields
pub source_port: usize,
pub result_port: usize,
pub queue_size: usize,
pub message_size: usize,
pub max_in_flight: usize,
pub report_refresh_interval_seconds: u64,
pub max_result_bytes: usize,
pub sink_writers: usize,
pub finalize_batch_size: usize,
pub finalize_flush_ms: u64,
pub lease_timeout_seconds: i64,
pub reap_interval_seconds: i64,
pub tcp_keepalive_idle_seconds: i32,
pub input_prefetchers: usize,
pub prefetch_max_entry_mb: usize,
pub prefetch_budget_mb: usize,
}Expand description
ZeroMQ dispatcher settings.
Fields§
§source_port: usizePort the ventilator listens on for worker task requests.
result_port: usizePort the sink listens on for worker results.
queue_size: usizeBatch size for task-store queue requests (also the in-memory dispatch queue size).
Must never exceed PostgreSQL’s max_locks_per_transaction setting.
message_size: usizeSize of an individual ZeroMQ message chunk, in bytes.
max_in_flight: usizeBackpressure threshold: the maximum number of in-flight (dispatched-but-unfinished) tasks the
ventilator tolerates before it stops leasing new work and mock-replies to requesting workers
(which back off and retry). This bounds the in-flight set so it drains via the sink as
results return, instead of growing toward the hard panic bound
crate::dispatcher::server::PROGRESS_QUEUE_HARD_LIMIT — graceful degradation under
overload rather than a crash (KNOWN_ISSUES D-6). Keep it well below that hard bound to
leave recovery headroom. In steady state the in-flight set is ~the worker count (~200), so
the default leaves a wide margin.
report_refresh_interval_seconds: u64How often (seconds) the finalize thread refreshes the report_summary rollup regardless of
drain, bounding report staleness while a long run is in flight (a conversion run can take
weeks, so drain-only refresh is not enough). This is the automatic freshness guarantee; with
REFRESH ... CONCURRENTLY the rebuild no longer blocks readers, so it is cheap to run often.
The cost is one rebuild’s DB load per interval (a few minutes at production scale). Default
1h.
max_result_bytes: usizeHard cap on the byte size of a single worker result the sink will write to /data. A
reply that exceeds it is rejected (the partial file is removed, the rest of the
multipart message is drained frame-by-frame to keep the socket in sync, and the task is
marked Invalid) rather than allowed to fill the disk — protecting the shared filesystem
from a runaway worker. We accept genuinely large jobs but draw the line here. Default 2
GiB.
sink_writers: usizeSink archive-writer pool size. Number of background threads the sink fans the blocking
/data result-archive writes out to (dispatcher rationalization phase 3, closes D-7). The
sink’s single ZMQ-PULL receive loop reads each result’s frames and hands them — task, then
streamed chunks, then a commit — to one of these writers, so receiving the next result is
no longer hostage to the current one’s slow QLC-RAID6 write + cortex.log parse. Per-task
ordering is preserved (a task’s frames go contiguously to one writer); fan-out is across
different tasks. Memory stays O(chunk) per writer (chunks are streamed and dropped, never
the whole archive resident) bounded by a small per-writer channel. Default 4 — a modest
decoupling that suits a box co-resident with ~200 workers; raise toward host cores if the
disk can absorb more concurrent writes. (1 is the floor; a single writer ≈ the legacy
inline behavior but still off the receive loop.)
finalize_batch_size: usizeFinalize batch coalescing — size threshold (N). The finalize thread accumulates returned
task reports and persists them to Postgres in one transaction per batch
(crate::backend::Backend::mark_done), flushing when the batch reaches this many reports —
or finalize_flush_ms elapses, whichever fires first. Larger N amortizes the DB round-trip
harder under load (fewer, bigger writes) at the cost of more rows per transaction. It is a
ceiling that mainly bites under burst/saturation; at steady-state load the time window
usually flushes first. Unlike queue_size, N is not bound by
max_locks_per_transaction (that limits object locks; mark_done takes only row locks),
so it can be large. Default 1024 — the empirical throughput knee from
examples/dispatcher_bench.rs (tasks/s rises to ~1024 then plateaus, and regresses by
~4096 where a single transaction holds row locks long enough to stall the pipeline; see
docs/DISPATCHER_BENCH.md). 1024 also bounds worst-case crash re-work to ~1024 tasks.
(Dispatcher rationalization phase 2, docs/archive/DISPATCHER_RATIONALIZATION.md.)
finalize_flush_ms: u64Finalize batch coalescing — time threshold (T), milliseconds. The maximum time a report
waits in an accumulating batch before it is flushed, bounding both report staleness and
worst-case crash re-work. An unflushed in-memory batch is never lost — its tasks stay
Queued and are recovered on restart — so T trades a little latency for far fewer DB writes,
not safety. At steady-state load this is usually the threshold that fires. Default 300 ms (at
~200 tasks/s it coalesces ~60 tasks per write instead of one write per task, for a few
hundred ms of staleness).
lease_timeout_seconds: i64Lease / visibility timeout (seconds). Base deadline for a dispatched (in-flight) task to
return a result before the reaper re-leases it. The effective per-task deadline backs off
with retries — (retries + 1) × lease_timeout_seconds from dispatch — so a task that keeps
timing out waits progressively longer rather than re-leasing ever-faster
(crate::helpers::TaskProgress::expected_at). This is the correctness net for a silently
dead / half-open worker (no ZMTP heartbeat needed): its task is recovered once the lease
lapses. Default 240 — just above the worker’s hard per-document timeout (the CorTeX
latexml fleet caps each conversion at 180 s and hard-kills the process on overrun), with
a 60 s margin, which bounds any single conversion’s runtime. With that cap, a lease expiry
reliably means the
worker died (timeout / OOM kill) on an unprocessable paper, so prompt re-lease — and,
after MAX_DISPATCH_RETRIES, dead-letter to Fatal — recovers the task within the run
instead of stranding it for an hour (orphaned-lease tail observed at scale). Raise it only
for a service whose workers have unbounded runtime (no per-task timeout). Paired with
reap_interval_seconds (how often the sweep runs).
reap_interval_seconds: i64Reaper sweep interval (seconds). How often the ventilator scans the in-flight set for
tasks past their lease_timeout_seconds deadline and re-leases / dead-letters them.
Decoupled from the request path so the in-flight set drains even under sustained
backpressure (KNOWN_ISSUES D-6). Kept well below the lease timeout so an expired task is
recovered promptly without scanning the set on every request. Default 60 s. (Lowering
both this and lease_timeout_seconds is what lets a fast chaos test exercise reaper-based
recovery in seconds instead of the hour-scale production timing.)
tcp_keepalive_idle_seconds: i32TCP keepalive idle (seconds) on the worker-facing ZMQ sockets (ventilator + sink). After
this many idle seconds the OS begins probing the peer; this both keeps idle worker
connections alive across NAT/firewall idle-timeouts — essential when the ~200 remote
workers reach the dispatcher over an overlay/VPN or any NAT’d path, where an idle mapping
is otherwise silently dropped and the worker falls out of the fleet until it reconnects —
and lets the OS reap a genuinely dead peer so the ROUTER doesn’t accumulate stale routes.
Task-recovery correctness does not depend on this (the lease reaper is that net, see
lease_timeout_seconds); it keeps the fleet connected. <= 0 leaves the OS keepalive
default (effectively off). Default 120, well under the common 5-minute NAT idle window.
(Probe interval/count are fixed sane
values in crate::dispatcher::server::apply_tcp_keepalive.)
input_prefetchers: usizeInput-archive prefetcher pool size (D-20). Number of background threads that warm the
next batch of task input archives into the OS page cache ahead of dispatch, so the
ventilator’s inline /data read is served from RAM (~0.02 ms) instead of the cold
QLC-RAID6 platter (~10 ms median — the single-thread dispatch ceiling at full-arXiv scale
where the working set ≫ RAM). The warmers open + read → discard; the warmed bytes are
reclaimable page cache, not dispatcher RSS, so this cannot OOM (the kernel drops the
cache before the workers’ anon memory). Graceful: a warm that lags or fails just leaves a
cold read, exactly as before. Default 8 (the warmers outpace dispatch ~8×, keeping the
window warm); 0 disables (the ventilator reads inline, the pre-D-20 behavior).
prefetch_max_entry_mb: usizePer-entry prefetch cap (MiB). Input archives larger than this are not prefetched — they fall through to the ventilator’s existing chunk-streaming read (O(chunk) resident, never the whole file), so the rare 50–100 MB monster streams cold instead of doubling its bytes in cache. Default 50.
prefetch_budget_mb: usizeTotal prefetch warm budget per batch (MiB). The warmers stop warming a fetch batch once
their cumulative warmed bytes reach this, so a batch that clusters large entries can’t dump
tens of GiB into page cache and churn out Postgres’s working set; the batch’s tail streams
cold. The typical batch (~queue_size × mean-entry ≈ a few hundred MiB) warms fully well
under this. Default 8192 (8 GiB).
Trait Implementations§
Source§impl Clone for DispatcherConfig
impl Clone for DispatcherConfig
Source§fn clone(&self) -> DispatcherConfig
fn clone(&self) -> DispatcherConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for DispatcherConfig
impl Debug for DispatcherConfig
Source§impl Default for DispatcherConfig
impl Default for DispatcherConfig
Source§impl<'de> Deserialize<'de> for DispatcherConfig
impl<'de> Deserialize<'de> for DispatcherConfig
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl JsonSchema for DispatcherConfig
impl JsonSchema for DispatcherConfig
Source§fn schema_name() -> String
fn schema_name() -> String
Source§fn schema_id() -> Cow<'static, str>
fn schema_id() -> Cow<'static, str>
Source§fn json_schema(generator: &mut SchemaGenerator) -> Schema
fn json_schema(generator: &mut SchemaGenerator) -> Schema
§fn is_referenceable() -> bool
fn is_referenceable() -> bool
$ref keyword. Read moreAuto Trait Implementations§
impl Freeze for DispatcherConfig
impl RefUnwindSafe for DispatcherConfig
impl Send for DispatcherConfig
impl Sync for DispatcherConfig
impl Unpin for DispatcherConfig
impl UnsafeUnpin for DispatcherConfig
impl UnwindSafe for DispatcherConfig
Blanket Implementations§
§impl<T> AggregateExpressionMethods for T
impl<T> AggregateExpressionMethods for T
§fn aggregate_distinct(self) -> Self::Outputwhere
Self: DistinctDsl,
fn aggregate_distinct(self) -> Self::Outputwhere
Self: DistinctDsl,
DISTINCT modifier for aggregate functions Read more§fn aggregate_all(self) -> Self::Outputwhere
Self: AllDsl,
fn aggregate_all(self) -> Self::Outputwhere
Self: AllDsl,
ALL modifier for aggregate functions Read more§fn aggregate_filter<P>(self, f: P) -> Self::Outputwhere
P: AsExpression<Bool>,
Self: FilterDsl<<P as AsExpression<Bool>>::Expression>,
fn aggregate_filter<P>(self, f: P) -> Self::Outputwhere
P: AsExpression<Bool>,
Self: FilterDsl<<P as AsExpression<Bool>>::Expression>,
§fn aggregate_order<O>(self, o: O) -> Self::Outputwhere
Self: OrderAggregateDsl<O>,
fn aggregate_order<O>(self, o: O) -> Self::Outputwhere
Self: OrderAggregateDsl<O>,
§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSend for T
impl<T> DowncastSend for T
§impl<T> DowncastSync for T
impl<T> DowncastSync for T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
§impl<T> IntoCollection<T> for T
impl<T> IntoCollection<T> for T
§fn into_collection<A>(self) -> SmallVec<A>where
A: Array<Item = T>,
fn into_collection<A>(self) -> SmallVec<A>where
A: Array<Item = T>,
self into a collection.fn mapped<U, F, A>(self, f: F) -> SmallVec<A>where
F: FnMut(T) -> U,
A: Array<Item = U>,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> IntoSql for T
impl<T> IntoSql for T
§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling [Attribute] value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi [Quirk] value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the [Condition] value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);