- All Implemented Interfaces:
Runnable
Runs every 5 seconds via SimpleTimer2. Each tick samples transport worker-stage CPU and updates the transport thread pool params; every 15th-second tick runs the full cycle for all other params. Each parameter implements an AIMD-style feedback loop bounded within safe ranges:
- Read observed stat (60s rolling average)
- Compute target value from observed stat + cross-references
- Clamp target to [min, max]
- Apply health-based dampening when system is degraded
- Accelerate rollback when param diverges from default
- Call applyValue() to push the new value to the router
New parameters are added by extending Tuner.BaseParam and implementing:
applyValue(int)— push value to router subsystemgetRuntimeValue()— read current value from subsystemgetObservedStat(RouterContext)— read primary signalcomputeTarget(double)— feedback logic with cross-refs
Params can be tuned live via the console UI or persisted to autotune.config (separate from router.config). Auto-revert to factory defaults triggers when system health drops below 0.3.
Nested Class Summary
Modifier and TypeClassDescriptionprivate classTunes ACK_FREQUENCY based on network round-trip time.private classTunes the peer-selection activity-window multiplier to the recency window that peer supply actually warrants.static classManages the autotune.config file — separate fromrouter.config.(package private) static classBase class for tunable params with history tracking and auto-revert.private classBuild first-hop delivery timeout.private classBuild handler max queue size.private classTunes BuildHandler thread count based on inbound build request backlog.private classBuild request reply timeout.private classTunes CoDel interval based on observed queue sojourn time.private classTunes CoDel target delay based on observed queue latency.private classTunes the timeout-rate threshold at which concurrent builds are throttled.private classCongestion avoidance growth rate factor.private classTunes connect timeout multiplier based on observed RTT.private classTunes DATA_MESSAGE_TIMEOUT based on observed send confirm time and LeaseSet lookup performance.private classEDH (Elligator2) key precalculation minimum pool size.private classBreadth (concurrent exploratory searches) of the exploratory search cycle.private classTunes the cooldown applied after repeated first-hop build failures.private classTunes how many first-hop failures within the cooldown window are required before a peer is skipped.private classGood deficit throttle interval.private classTunes I/O handler thread priority based on dispatch latency and queue backlogs.private classTunes the Tuner-managed default client concurrent-connection cap (i2ptunnel.maxConnectionsdefault).private classTunes I2PTunnel client runner max pool size.private static classReflection helper for accessing TunnelControllerGroup across module boundaries.private classTunes the number of inbound connections that may wait in the I2PTunnel server handler bounded queue before overflow rejection.private classTunes the I2PTunnel server handler thread pool size.private classTunes the timeout after which a stalled I2PTunnel server connection is closed.private classTuner param for the I/O transfer pool size (Server→Client data forwarding).private classTunes the Tuner-managed default per-tunnel cap on server handler threads.private classTunes MAX_IB_ESTABLISH_TIME based on inbound establish time stat.private classTunes PumpedTunnelGateway MAX_IB_MSGS_PER_PUMP based on gateway queue depth.private classDelay before sending an ACK for a duplicate or out-of-order packet.private classInactivity timeout for streaming connections.private classInitial concurrent messages per peer — starting point for new connections.private classTunes streaming INITIAL_ACK_DELAY based on sends-before-ACK stat.private classDelay before the first retransmission of an unACKed packet.private classTunes streaming INITIAL_RTO based on observed RTT and congestion signals.private classTunes streaming INITIAL_WINDOW_SIZE based on observed RTT.private classInitial RTO for new UDP peers — starting point before RTT estimation kicks in.private classTunes I2CP internal client queue size.private classMax peers a lease is republished to after a successful search, on top of the peers that already answered.private classTunes the maximum concurrent next-hop RouterInfo lookups.private classLossy peer demotion threshold.private classLeaseSet lookup deadline cap (ms).private classTunes maximum concurrent tunnel builds based on success rate and backlog.private classMax concurrent SSU session establishment.private classTunes max concurrent outbound messages per peer.private classTunes max message dispatch age — messages older than this are dropped instead of cycling through bid/send/fail/requeue until expiry.private classMaximum fast-tier peers.private classMaximum high-capacity peers.private classPer-connection inbound buffer cap (bytes).private classMax participating tunnels.private classMax peer profiles in memory.private classTunes max queued outbound SSU connections.private classMax delay between retransmissions.private classMax retransmissions before giving up.private classMax retransmission timeout.private classCaps the RTT estimate to prevent pathological values from breaking RTO.private classMax send window (CWIN) — bytes in flight per peer.private classTunes streaming max slow start window.private classElastic, demand-following cap on concurrent streams shared by the streaming subsystem.private classMaximum SYN sends before giving up on a handshake.private classTunes the streaming maximum congestion window, scaled to available memory.private classTunes max write buffers per NTCP connection.private classThreshold for the message-processing-latency transit throttle (router.defaultProcessingTimeThrottle, read by RouterThrottleImpl).private classMinimum concurrent messages per peer — floor below which we never drop.private classMinimum fast-tier peers.private classMinimum high-capacity peers.private classTunes the minimum pacing rate for streaming connections.private classMin delay between retransmissions.private classMinimum RTO — floor for retransmission timeout.private classML-KEM key precalculation minimum pool size.private classNetDB max concurrent searches.private classNetDB total search time cap (ms) — the hard deadline each iterative search works within, bounding the LS deadline cap and the per-hop message window.private classNetDB search limit (max peers per iterative search).private classNetDB single search time (per-peer reply budget, ms).private classTunes NTCP ESTABLISH_TIMEOUT based on outbound establish time stat.private classTunes NTCP failsafe iteration frequency based on connection count.private classNTCP send finisher queue capacity.private classTunes NTCP reader thread count based on send path latency.private classTunes NTCP send finisher thread count based on send pool utilization and send finisher queue depth.private classTunes NTCP writer thread count based on send path latency.private classTunes the outbound SSU2 handshake timeout.private classTunes PumpedTunnelGateway MAX_OB_MSGS_PER_PUMP based on gateway queue depth.private classOutbound message expiration timeout.static classSnapshot of a subsystem's tunable parameter values at a point in time.private classTunes the maximum per-participant transit tunnel limit in ParticipatingThrottler.private classTunes the minimum per-participant transit tunnel limit inParticipatingThrottler.private classTunes the percentage-based transit tunnel limit in ParticipatingThrottler.private classTunes streaming PASSIVE_FLUSH_DELAY based on message send size stat.private classPer-peer outbound message queue size.private classTunes the percentage of participating tunnels used to throttle concurrent next-hop RI lookups for incoming tunnel build requests.private classPer-tunnel BWE divisor.private classTunes the pool backoff duration.private classTunes the consecutive failure threshold for pool backoff.private classPost-RTO window restart size, in MTUs.private classTunes the EventPumper max idle loop rate (ntcp.pumper.maxIdleLps) against the observed NTCP loop rate.private classTunes TunnelGatewayPumper queue capacity based on gateway queue depth.private classTunes TunnelGatewayPumper thread count based on pumper backlog.private classTunes rDNS executor core pool size based on queue depth.private classSyntheticREDQueue maximum drop probability.private classSyntheticREDQueue maximum queue threshold (bytes).private classSyntheticREDQueue minimum queue threshold (bytes).private classTunes FIFOBandwidthRefiller REPLENISH_FREQUENCY based on bandwidth utilization.private classTunes the high-load CPU threshold in RequestThrottler.private classTunes the high-load job lag threshold in RequestThrottler.private classTunes how much system load inflates the effective request count/limit ratio.private classTunes the moderate-load CPU threshold in RequestThrottler.private classTunes the moderate-load job lag threshold in RequestThrottler.private classTunes the request rejection steepness — 100=linear, 200=quadratic.private classTunes the request rejection threshold (%) — start rejecting requests at count/limit ratio this high.private classTunes how long moderate load must persist before disconnecting low-share peers.private classTunes the 1-second burst detection threshold inRequestThrottler.private classTunes the maximum per-peer request throttle limit.private classTunes the minimum per-peer request throttle limit.private classTunes the percentage-based request throttle limit.private classTunes TunnelGatewayPumper REQUEUE_TIME based on pumper queue depth.private classLease republish message window (ms) — how long the store sent to heal the network after a successful search may live.private classRouterInfo lookup deadline cap (ms), used for transit next-hop lookups.private classRTO backoff multiplier percentage (100 = 1.0x, 125 = 1.25x).private classTunes EventPumper SELECTOR_LOOP_DELAY based on NTCP loop rate.private classTunes NTCP send pool capacity based on peer count, latency, backlog, and eviction rate.private classSSU2 sent-messages cleanup interval.private classSlow start growth rate factor.private classTunes the TCP connect timeout for I2PTunnel server socket connections.private static classCPU saturation state of one transport worker stage.private classTunes the stale-build pruning threshold percentage.private classTunes inbound packet receive worker shards (i2p.streaming.receiveWorkerThreads).private static classReflection helper for the streaming module's tuned statics.static classPer-subsystem health scores.(package private) static classTracks overall router health score for adaptive tuning decisions.private classTunes maximum delay between tests per tunnel.private classTunes the maximum tunnel test failure window (i2p.tunnel.testJob.maxTestPeriod).private classTunes max concurrent test jobs.private classTunes minimum delay between tests per tunnel.private classTunes the minimum tunnel test failure window floor (i2p.tunnel.testJob.minTestPeriod).(package private) static final classThread-id keyed sample state forsampleStageCpu(): per live thread, whichCPU_STAGESentry its name matched and the CPU nanoseconds recorded at the previous sample.private classTunnel rejection curve steepness.private classTunes the global max window size ceiling for all streaming connections.private classTunes how much system load inflates the effective count/limit ratio.private classTunes the transit rejection steepness — 100=linear, 200=quadratic (faster ramp at high ratios).private classTunes the transit rejection threshold (%) — start rejecting at count/limit ratio this high.private classTransit message throttle factor.static interfaceA tunable parameter with name, description, bounds, and default value.private classTunnel growth factor for probabilistic acceptance.private classTunes the tunnel IV filter size.private classTunes the spare tunnel buffer above target for each pool.private classUDP packet handler thread count.private classMaximum RTO — ceiling for retransmission timeout.private classTunes UDP MessageReceiver thread count based on upstream pressure.private classTunes the untested tunnel cap multiplier for each pool.private classTunes Westwood+ EWMA decay factor based on bandwidth stability.private classTunes I2CP CLIENT_WRITER_QUEUE_SIZE based on network load.private classX25519 key precalculation minimum pool size.Field Summary
Modifier and TypeFieldDescriptionprivate final Tuner.AutotuneConfigThe autotune settings read from the config.private final RouterContextThe router context supplying stats and config.private final Set<Tuner.TunableParam> Transport worker thread params updated every 5s instead of the 15s cycle.private longWall clock of the previous CPU sample cycle.private longTime of the last fast tuning pass.private Tuner.SystemHealthHealth from the most recent slow cycle, shared with fast-cycle params.private doubleOverall health score from the last computation, NaN before the first.Last failure-log time per param name, for the rate limit.private longTime of the last slow tuning pass.Per-subsystem scores from the last computation.private final LogLogger for this class.Params whose update failure has already been logged at least once.private final List<Tuner.TunableParam> The tunable parameters, in registration order.Classes named by_reflectorTargets, loaded on first use.Methods whose reflective call has already been reported, keyed class#name.The class each reflectively-tuned static is declared on, keyed by method name.private final Map<String, Tuner.StageCpu> Per-stage CPU state, updated each cycle bysampleStageCpu().private final Tuner.ThreadCpuTablePer-thread stage and previous CPU time, keyed by thread id.private static final ThreadMXBeanPer-thread CPU time source; getThreadCpuTime() returns -1 on unsupported JVMs.private long[]Scratch buffer for the name lookups that resolve newly seen threads.private static intExtra tunnels to build beyond target per pool when build failure rate is high.(package private) static final intThe ceiling should climb.private static final doubleFraction of the lossy threshold below which the ceiling may climb again.(package private) static final intWindows the signal must hold one direction before the ceiling moves in it.(package private) static final intThe ceiling should hold where it is.(package private) static final intThe ceiling should shrink.private static final doubleHeap % below which a clean path may take a two-step climb; holds at or above this until memory frees.private static intCached CoDel interval in ms, -1 until first set.private static intThe last autotuned CoDel target and interval, in ms.private static final longThe gateway send-failure lifetime above which the ceiling treats the path as congested.private static final intCore count for scaling, at least 1.private static final intCPU cores available.private static final String[]Transport worker thread name prefixes tracked for CPU saturation.private static final intLargest EDH precomputation pool allowed.private static final intSmallest EDH precomputation pool allowed.static final intLowest value either establish-timeout param may be tuned to.private static intPriority for I/O handler threads — boosted under load, reduced when idle.private static final intConsecutive cycles with ample peer supply required before the activity window tightens again.private static intMinimum INITIAL_RTO target (ms) in the Tuner'scomputeTarget(double).private static intI2CP internal queue size — static so ClientManager can read it without circular dep.private static final booleanTrue on a machine too slow for the default budgets.(package private) static final intIdle target for the NetDB max concurrent searches param.(package private) static final intMax history samples for sparklines (120 samples @ 15s = 30min; transport thread params sample every 5s = 10min).private static final longMaximum heap available, in bytes.private static intMax time (ms) a message may sit in the outbound dispatch queue before being dropped.private static final intMemory scale factor, one per 256 MB of heap, at least 1.(package private) static final intCompleted outgoing NetDb searches that must have finished before a failure ratio is trusted.(package private) static final intCompleted tunnel test rounds needed before a failure ratio is trusted.private static final intML-KEM precalc min/max — each pair ~3.5KB, scale with cores and memory.private static final intLargest ML-KEM precomputation pool allowed.private static final intSmallest ML-KEM precomputation pool allowed.private static final longMinimum gap between repeated failure logs for the same param, ms.(package private) static final StringCounts params that threw during a tuning cycle.(package private) static final StringProperty prefix for tuner ranges.(package private) static final doubleOutgoing-search failure ratio above which no NetDb search budget may be tightened, and above which the budgets grow instead.(package private) static final intIdle autotune target for the I2PTunnel server handler queue capacity.private static final doubleHeap % above which the streaming ceiling shrinks half a step.(package private) static final StringStat prefix for per-stage CPU usage (pct of one core, 100 = full core).(package private) static final intFloor and default for the I/O stall window, ms.private static final longMinimum uptime (ms) before system health scoring begins.(package private) static final longPeriod for short-term bandwidth stats.(package private) static final longPeriod for long-term bandwidth stats.(package private) static final doubleStreaming retransmission rate at or above which loss is called lossy.private static final intMatches ConnectionOptions.DEFAULT_MAX_MESSAGE_SIZE (1730), which is what the streaming layer actually applies: i2ptunnel builds socket manager options without maxMessageSize, so connections fall back to this default.private static final doubleStreaming retransmission rate at or above which loss is severe enough to take the doubled shrink step instead of the ordinary one.private static final doubleHeap % below which the streaming ceiling takes the full four-step jump.static final StringCongestion subsystem identifier.static final StringCrypto subsystem identifier.static final StringI2CP subsystem identifier.static final StringNetDB subsystem identifier.static final StringPeers subsystem identifier.static final StringRouter subsystem identifier.static final StringStreaming subsystem identifier.static final StringTransit subsystem identifier.static final StringSubsystem labels for grouping in the UI.static final StringTunnel subsystem identifier.(package private) static final doubleTunnel-test failure ratio above which the test schedule may not be relaxed.private static intPer-pool test budget for client tunnels — high enough to never throttle.private static intRetest delay backoff multiplier (percent, 100 = 1x).private static final intBacklog at or above which the timer gains a worker, in ms.private static final intBacklog at or below which the timer releases a worker, in ms.private static intUntested tunnel cap multiplier, tuned in-memory only.private static final intX25519/EDH precalc — each pair ~64 bytes, scale with cores and memory.private static final intLargest X25519 precomputation pool allowed.private static final intSmallest X25519 precomputation pool allowed.Fields inherited from class SimpleTimer2.TimedEvent
_state, DEFAULT_FUZZModifier and TypeFieldDescriptionprotected SimpleTimer2.TimedEventStateState of the current event.protected static final intDefault fuzz threshold in milliseconds.Constructor Summary
ConstructorsMethod Summary
Modifier and TypeMethodDescriptionstatic voidAdjust the current thread's priority to the target handler priority.private static voidapplyStartupTunables(Tuner.AutotuneConfig autotune, RouterContext ctx) Apply the configured values at startup, clamped to the tunable ranges enforced by MaxDispatchAgeParam / HandlerThreadPriorityParam.private static voidbindReflectorTargets(String cls, String... names) Record the class that declares a group of reflectively-called statics.(package private) static doublecallStaticDouble(Class<?> c, String name, double fallback) Invoke a static double getter, or returnfallbackif it cannot be reached.(package private) static intcallStaticInt(Class<?> c, String name, int fallback) Invoke a static int getter, or returnfallbackif it cannot be reached.(package private) static longcallStaticLong(Class<?> c, String name, long fallback) Invoke a static long getter, or returnfallbackif it cannot be reached.(package private) static voidcallStaticSetInt(Class<?> c, String name, int value) Invoke a static int setter, reporting a call that cannot be made.(package private) static voidcallStaticSetLong(Class<?> c, String name, long value) Invoke a static long setter, reporting a call that cannot be made.(package private) static intceilingDirection(double lossRate, int riseShrink, int riseClimb) Which way the ceiling wants to move, with a dead-band between the two directions.(package private) static intThe floor the tuner may propose for i2ptunnel.clientRunner.max.(package private) static intcomputeClientMaxConnections(int current, int min, int max, double shedRate, double activeThreads, int maxRunner) Pure decision logic for the Tuner-managed default client concurrent-connection cap (i2ptunnel.maxConnectionsdefault).(package private) static intcomputeIOTransferTarget(int current, double observed, int min, int max) Pure growth policy for the global Server→Client I/O budget.(package private) static intcomputeReceiveWorkers(int current, int min, int max, double backlogged, double queueDepth, double rtxRatio, double jobLag) Pure decision logic for the Tuner-managed inbound receive-worker default (i2p.streaming.receiveWorkerThreads).(package private) static intcomputeServerBacklogQueueCapacity(int current, int min, int max, double queueDepth, double jobLag) Pure decision logic for the I2PTunnel server handler queue capacity.(package private) static intcomputeServerHandlerThreads(int current, int min, int max, double queueDepth, double active, double blockingTime, double jobLag, double synExpire) Pure decision logic for the I2PTunnel server handler pool size.(package private) static intcomputeStreamingBdpTarget(double bwSendBps, double rttMs, int messageSize, int min, int max) Compute the streaming congestion-window target from the bandwidth-delay product.(package private) static intcomputeStreamingMaxWindowTarget(int current, int min, int max, int step, int defaultValue, double failLifetime, double lossRate, double memPct) Compute the streaming max-window ceiling target for one tuning cycle.private voidCompute derived tuning values from subsystem scores and job queue stats.(package private) static intcongestionAvoidanceGrowthTarget(int current, int min, int max, int step, int defaultValue, double buildSuccess, double failLifetime, double lossRate, double lifetimeRTT, double lifetimeWindowSize, double chokeSize, double congWindowSize, double rtxRatio, double rtxRatio5) Compute the target forCongestionAvoidanceGrowthParam.private booleanWhether the JVM has CPU headroom to add worker threads.(package private) static intestablishTimeoutTarget(double observed, int min) Target establish timeout for an observed mean handshake duration.(package private) static intexponentialClimb(int current, int step) Clean-path climb for a window-sized parameter:max(step, current / 4).private voidfastCycle(long now) Fast 5s cycle: transport worker thread pools react to CPU saturation.(package private) static MethodfindStaticMethod(Class<?> c, String name, Class<?>... args) Find a static method reflectively and make it callable.Shared AutotuneConfig instance.private doublegetAvgStat(String name) 60s rolling average of the named stat, or NaN if unavailable.static intThe build failure buffer.private doubleBuild success rate from hourly stats (0.0-1.0).private doublegetEventCountStat(String name) Event count in the last 60s for the named stat, or NaN if unavailable.static intThe handler thread priority.doubleLatest system health score (0.0 = worst, 1.0 = perfect).private doublegetHourlyAvgStat(String name) Hourly rolling average of the named stat, or NaN if unavailable.static intThe I2CP internal queue size.private doubleJob queue pressure score (0.0 = idle, 1.0 = severe).static intThe max dispatch age in ms.static doubleReturns current heap memory pressure as a ratio from 0.0 (no pressure) to 1.0 (heap full).private NTCPTransportNTCP transport, or null if not available.Tunable param by name.Snapshots of all tunable params.Per-subsystem health scores computed during the last tuning cycle.static intThe test client budget.static intThe test retest backoff.private UDPTransportUDP transport, or null if not available.(package private) static intThe ceiling the tuner may propose for udp.peer.initConcurrentMsgs.(package private) static intThe largest queue sizesetInternalQueueSize(int)accepts.(package private) static intThe smallest queue sizesetInternalQueueSize(int)accepts.(package private) static booleanisHealthyBuildRate(double observed) Whether a build success rate counts as healthy for a tuning walk.(package private) static booleanisStreamLossSevere(double lossRate) Is the streaming path losing enough to justify the aggressive shrink?(package private) static booleanisStreamLossy(double lossRate) Is the streaming path losing enough that windows should stop growing?(package private) static intlookupLimitTarget(int current, int min, int max, int step, boolean lookupsSlow, boolean queueBackedUp, boolean dropsHappening, boolean lookupsFast) Pure starvation-first decision for next-hop lookup concurrency (see PercentLookupLimitParam): queue full or overflowing while lookups succeed in time → demand exceeds the lid; raise aggressively so queued entries convert instead of expiring as silent build drops lookups genuinely slow with nothing queued → netdb overload; less vetting is cheaper fast and quiet → more vetting is cheap anything else (mixed signals) → hold(package private) static intmaxSlowStartWindow(int current, int min, int max, int step, int factoryDefault, double observed, double failLifetime, double lossRate) Compute the target value forMaxSlowStartWindowParam.(package private) static StringmaxWindowDecision(double failLifetime, double lossRate, double memPct, long dupEvents, long sendEvents, double avgDupSize, double eventRatio) Say why the streaming ceiling took the value it did.(package private) static StringmaxWindowDecisionFrom(double failLifetime, double driverRate, double eventRate, double memPct, long dupEvents, long sendEvents, double avgDupSize) Assemble the ceiling's decision reason from its signals, with the two loss rates named so the slot assignment cannot be transposed.private static intMemory-derived ceiling for the I2PTunnel client worker pool and its admission gate.private static intMemory-derived ceiling for per-peer initial concurrent messages: ~1 message per 8MB of max heap, clamped to a sane band.(package private) static intThe ceiling the tuner may propose for crypto.mlkem.precalcMin.(package private) static intnextStallTimeoutMs(int current, double observed, int minMs, int maxMs) Next I/O stall window for an observed per-cycle stall delta.(package private) static intnextWindowMultiplier(int fastPeers, boolean startupGrace, int healthyCycles, int min, int max, int step) Next window multiplier for a tuning cycle, keyed on peer supply.private voidRecord that a param's tuning cycle threw.(package private) static doubleoutcomeRatio(double successes, double failures, int minSamples) Failure ratio from per-attempt outcome event counts.private static Stringpct(double rate) A fraction as a whole-percent string; NaN becomes "no signal".private intpeerScaledCap(Transport t, int min, int max, int divisor) Cap pool size by active transport connections: one extra thread above the minimum perdivisorconnections.(package private) static doubleperPeerSearchTimeMs(double successTime, double successRetries) Per-peer reply budget implied by a completed successful search.private voidRemove old tuner.* properties fromrouter.configthat were written there before autotune.config was introduced.(package private) static StringreflectorFailureKey(Class<?> c, String name) The identity a reflective failure is deduplicated by.(package private) static Class<?> reflectorTargetClass(String name) The class a reflectively-called static is declared on, loaded on demand.The target table, for the test that pins it against the compiled classes.private voidRefresh XDH, EDH and MLKEM precomputation pool sizes.(package private) static voidreportReflectorFailure(Class<?> c, String name, Throwable t) Report a reflective failure once per method.private voidresolveThreadNames(long[] ids, int count) Read the names of threads this cycle has not classified yet.voidRestore all params to factory defaults (code-level, not persisted).private voidSample per-thread CPU time per stage, once per fast tune cycle (5s).(package private) static intscaleForSystem(int base, int hardMin, int hardMax) Compute a system-scaled value: base * factor, bounded by min and max.(package private) static doublesearchFailureRatio(double successes, double failures) Failure ratio of this router's own outgoing NetDb searches.private static Stringsecs(double ms) A millisecond duration as a seconds string with one decimal.(package private) static intsendWindowTarget(int current, int min, int max, int step, int idleFloor, double observed, double jobLag, double memPressure, double retransmits, double congCWIN, double sendWindow, double rtxRatio, double failsafeCloses, double mtuDecrease) Pure CWIN growth/shrink decision for the max send window, extracted fromMaxSendWindowParam#computeTargetso the branches are unit-testable without a Tuner context.(package private) static voidsetHandlerThreadPriority(int priority) Records the tuned I/O handler thread priority, so the verify andgetHandlerThreadPriority()read one value.static voidsetInternalQueueSize(int size) The I2CP internal queue size, clamped to a sane range (called by Tuner).(package private) static voidsetMaxDispatchAgeMs(int value) Records the tuned dispatch age.voidsetOverride(String name, int value) An override value for a param.(package private) static booleanshouldReportReflectorFailure(Class<?> c, String name) Whether a reflective failure has not been reported yet, claiming the one report it is owed.voidshutdown()Flush on shutdown — unconditional write if dirty.private voidslowCycle(long now) Slow 15s cycle: system health, param updates, crypto pools and derived values.(package private) static intslowStartGrowthTarget(int current, int min, int max, int step, int defaultValue, double observed, double buildSuccess, double failLifetime, double lossRate, double lifetimeWindowSize, double chokeSize, double congWindowSize, double rtxRatio, double rtxRatio5) Compute the target forSlowStartGrowthParam.(package private) static intStage a thread belongs to, by name.private intstageGrowth(String stage) Thread growth step for a CPU-saturated stage: +1 at >= 75% of a core sustained over two cycles, +2 at >= 150%, +3 at >= 300%.(package private) static intstreamCeilingTarget(double active, int current, int min, int max, int step, boolean refusing, boolean distress) Pure decision logic for the stream ceiling.(package private) static doublestreamingLossRate(double dupEvents, double sendEvents, double rtxPerMille, double rtxBytesPerMille) Compute the recent streaming retransmission rate.(package private) static doublestreamingLossRate(double dupEvents, double sendEvents, double dupMessages, double messagesSent, double rtxPerMille, double rtxBytesPerMille) The retransmission rate, preferring the volume-weighted view.voidExpire entries and reschedule the next check.(package private) static inttunedOr(int tuned, int fallback) The value a param last applied, or the config-or-default reading when nothing has been applied yet.private voidupdateSlowParams(Tuner.SystemHealth health) Update all non-fast params on the 15s cycle.private static StringwholePct(double percent) A value already expressed in percent, as a whole-percent string.private static booleanwindowsCongesting(double congWindowSize) Whether the congestion window at recent congestion events was too small to count as a real congestion signal — the shared "window collapsed" predicate for both streaming growth-factor targets, so the two cannot drift apart.Methods inherited from class SimpleTimer2.TimedEvent
cancel, forceReschedule, reschedule, reschedule, run, schedule, setFuzz, setPool, toStringModifier and TypeMethodDescriptionbooleancancel()Cancel the timed event.voidforceReschedule(long timeoutMs) Always use the new time - ignores fuzz.voidreschedule(long timeoutMs) Use the earliest of the new time and the old time May be called from within timeReached(), but schedule() is better there.voidreschedule(long timeoutMs, boolean useEarliestTime) May be called from within timeReached(), but schedule() is better there.voidrun()voidschedule(long timeoutMs) Schedule this event.voidsetFuzz(int fuzz) Reschedule threshold in ms.voidsetPool(SimpleTimer2 pool) Timer pool for this event.toString()Timer summary for log messages.
Field Details
_context
The router context supplying stats and config._log
Logger for this class._params
The tunable parameters, in registration order._autotune
The autotune settings read from the config._fastParams
Transport worker thread params updated every 5s instead of the 15s cycle._lastFastCycle
private long _lastFastCycleTime of the last fast tuning pass._lastSlowCycle
private long _lastSlowCycleTime of the last slow tuning pass._lastHealth
Health from the most recent slow cycle, shared with fast-cycle params.STAT_PERIOD
STAT_PERIOD_LONG
static final long STAT_PERIOD_LONGPeriod for long-term bandwidth stats.- See Also:
MAX_HISTORY
static final int MAX_HISTORYMax history samples for sparklines (120 samples @ 15s = 30min; transport thread params sample every 5s = 10min).- See Also:
STAGE_STAT_PREFIX
Stat prefix for per-stage CPU usage (pct of one core, 100 = full core).- See Also:
CPU_STAGES
Transport worker thread name prefixes tracked for CPU saturation._threadMXBean
Per-thread CPU time source; getThreadCpuTime() returns -1 on unsupported JVMs._stageCpu
Per-stage CPU state, updated each cycle bysampleStageCpu()._threadCpu
Per-thread stage and previous CPU time, keyed by thread id._unseenThreadIds
private long[] _unseenThreadIdsScratch buffer for the name lookups that resolve newly seen threads._lastCpuSampleMs
private long _lastCpuSampleMsWall clock of the previous CPU sample cycle.internalQueueSize
private static volatile int internalQueueSizeI2CP internal queue size — static so ClientManager can read it without circular dep.untestedMultiplier
private static volatile int untestedMultiplierUntested tunnel cap multiplier, tuned in-memory only. No subsystem consumer readsi2p.tunnel.untestedMultiplier, so the value lives in this static (notrouter.config) purely to track the last autotuned setting across applyValue/getRuntimeValue calls. -1 = no tuned value yet; use the config-or-default path.maxDispatchAgeMs
private static volatile int maxDispatchAgeMsMax time (ms) a message may sit in the outbound dispatch queue before being dropped.codelTargetMs
private static volatile int codelTargetMsThe last autotuned CoDel target and interval, in ms. The queues carry the value per instance and offer no reading, and the tuning path never writes therouter.codelTarget/router.codelIntervalconfig properties, so reading those back reported a value that never moved after an apply. LikeuntestedMultiplierthese track the last autotuned setting only. -1 = no tuned value yet; use the config-or-default path.codelIntervalMs
private static volatile int codelIntervalMsCached CoDel interval in ms, -1 until first set.handlerThreadPriority
private static volatile int handlerThreadPriorityPriority for I/O handler threads — boosted under load, reduced when idle.testClientBudget
private static volatile int testClientBudgetPer-pool test budget for client tunnels — high enough to never throttle. Global caps (maxQueuedTests, hardLimit) protect the job queue instead.testRetestBackoff
private static volatile int testRetestBackoffRetest delay backoff multiplier (percent, 100 = 1x). Increases under job queue pressure to free test capacity for UNTESTED tunnels. Range 100 (no backoff) to 800 (8x).buildFailureBuffer
private static volatile int buildFailureBufferExtra tunnels to build beyond target per pool when build failure rate is high. Computed from build success rate. Range 0-10.initialRtoFloor
private static volatile int initialRtoFloorMinimum INITIAL_RTO target (ms) in the Tuner'scomputeTarget(double). Prevents the Tuner from setting RTO too low, which causes spurious retransmits and wasted bandwidth. Default 6000ms — accommodates typical I2P RTT up to ~3s without premature retransmit (coherent with the 9000ms streaming-side default); the Tuner raises from there for higher-latency paths.SUB_TRANSPORT
Subsystem labels for grouping in the UI.- See Also:
SUB_TUNNEL
SUB_STREAMING
Streaming subsystem identifier.- See Also:
SUB_I2CP
SUB_CONGESTION
Congestion subsystem identifier.- See Also:
SUB_CRYPTO
SUB_ROUTER
SUB_NETDB
SUB_PEER
SUB_TRANSIT
MAX_MEMORY
private static final long MAX_MEMORYMaximum heap available, in bytes.CORES
private static final int CORESCPU cores available.IS_SLOW
private static final boolean IS_SLOWTrue on a machine too slow for the default budgets.MEM_FACTOR
private static final int MEM_FACTORMemory scale factor, one per 256 MB of heap, at least 1.CORE_FACTOR
private static final int CORE_FACTORCore count for scaling, at least 1.MLKEM_FACTOR
private static final int MLKEM_FACTORML-KEM precalc min/max — each pair ~3.5KB, scale with cores and memory.MLKEM_PRECALC_MIN
private static final int MLKEM_PRECALC_MINSmallest ML-KEM precomputation pool allowed.MLKEM_PRECALC_MAX
private static final int MLKEM_PRECALC_MAXLargest ML-KEM precomputation pool allowed.XDH_FACTOR
private static final int XDH_FACTORX25519/EDH precalc — each pair ~64 bytes, scale with cores and memory.XDH_PRECALC_MIN
private static final int XDH_PRECALC_MINSmallest X25519 precomputation pool allowed.XDH_PRECALC_MAX
private static final int XDH_PRECALC_MAXLargest X25519 precomputation pool allowed.EDH_PRECALC_MIN
private static final int EDH_PRECALC_MINSmallest EDH precomputation pool allowed.EDH_PRECALC_MAX
private static final int EDH_PRECALC_MAXLargest EDH precomputation pool allowed._paramFailureLogged
_lastParamFailureLog
PARAM_FAILURE_LOG_INTERVAL_MS
private static final long PARAM_FAILURE_LOG_INTERVAL_MSMinimum gap between repeated failure logs for the same param, ms.- See Also:
PARAM_UPDATE_FAILED_STAT
Counts params that threw during a tuning cycle._lastHealthScore
private volatile double _lastHealthScoreOverall health score from the last computation, NaN before the first._lastSubsystemScores
PROP_PREFIX
Property prefix for tuner ranges. Format: tuner.{paramName}.min, tuner.{paramName}.max, tuner.{paramName}.step.- See Also:
_reflectorFailures
_reflectorTargets
The class each reflectively-tuned static is declared on, keyed by method name.Streaming and i2ptunnel are not on the router's compile classpath, so the tuner reaches their statics by name. Choosing the class at the call site is what broke: three params named a real method on the wrong class, the lookup failed, the failure was swallowed, and the param tuned nothing while its getter handed back a fallback that read like a live value. The class is declared once here instead, and TunerReflectorTargetTest pins every entry against the classes as they are actually compiled.
_reflectorClasses
Classes named by_reflectorTargets, loaded on first use.ESTABLISH_TIMEOUT_MIN
public static final int ESTABLISH_TIMEOUT_MINLowest value either establish-timeout param may be tuned to.Measured on a live router: at 2250 the outbound path abandoned roughly 117 session establishments per minute, every one expiring on the deadline rather than on an observed peer failure, and 99.6% of them inside the SSU2 token exchange, which costs one round trip more than SSU1. Raising the budget to 5000 took that to zero - but build success fell from 64.5% to 56.0% and throughput 12%, because the patience moves out of the establishment path and into the build slot. 4000 is the compromise: comfortably above the value known to be harmful, below the one where slot occupancy starts costing builds. The original floor of 1500 sat well below the harmful value, so a slider drag or a restore-defaults could silently reintroduce the failure.
- See Also:
STREAM_MSG_SIZE_DEFAULT
private static final int STREAM_MSG_SIZE_DEFAULTMatches ConnectionOptions.DEFAULT_MAX_MESSAGE_SIZE (1730), which is what the streaming layer actually applies: i2ptunnel builds socket manager options without maxMessageSize, so connections fall back to this default. Hardcoded here because the router module cannot reference the streaming app constant.- See Also:
STREAM_LOSSY_RATE
static final double STREAM_LOSSY_RATEStreaming retransmission rate at or above which loss is called lossy. Package-visible so the ceiling's dead-band can be derived from it in tests rather than re-declared as a second literal that could drift from it.- See Also:
STREAM_SEVERE_LOSS_RATE
private static final double STREAM_SEVERE_LOSS_RATEStreaming retransmission rate at or above which loss is severe enough to take the doubled shrink step instead of the ordinary one.Split from
STREAM_LOSSY_RATEso a mildly lossy path still backs off (single step) but is not torn down at full aggression every cycle — before this split,sendDuplicateSize > 1000put every cycle on the aggressive tier because the threshold was a size, not a rate.- See Also:
CONGESTION_GATE_MS
private static final long CONGESTION_GATE_MSThe gateway send-failure lifetime above which the ceiling treats the path as congested.- See Also:
CEILING_CLIMB_FRACTION
private static final double CEILING_CLIMB_FRACTIONFraction of the lossy threshold below which the ceiling may climb again.The same threshold would otherwise serve both directions, so a signal sitting on it crosses it repeatedly and the ceiling bangs between the floor and the cap without resting in between - which is what a measured 50-second limit cycle looked like before this gap existed.
- See Also:
CEILING_CONFIRM_WINDOWS
static final int CEILING_CONFIRM_WINDOWSWindows the signal must hold one direction before the ceiling moves in it.A single reading is one period of a small-sample ratio, which crosses a threshold on its own. Two consecutive periods in the same direction is the cheapest debounce that still reacts within a loss episode rather than after it.
- See Also:
CEILING_SHRINK
CEILING_CLIMB
CEILING_HOLD
STRONG_CLEAN_MEM_PCT
private static final double STRONG_CLEAN_MEM_PCTHeap % below which the streaming ceiling takes the full four-step jump.- See Also:
CLIMB_HEADROOM_MEM_PCT
private static final double CLIMB_HEADROOM_MEM_PCTHeap % below which a clean path may take a two-step climb; holds at or above this until memory frees.- See Also:
SHRINK_MEM_PCT
private static final double SHRINK_MEM_PCTHeap % above which the streaming ceiling shrinks half a step.- See Also:
TIMER_GROW_MS
private static final int TIMER_GROW_MSBacklog at or above which the timer gains a worker, in ms.- See Also:
TIMER_SHRINK_MS
private static final int TIMER_SHRINK_MSBacklog at or below which the timer releases a worker, in ms.- See Also:
MIN_SEARCH_SAMPLES
static final int MIN_SEARCH_SAMPLESCompleted outgoing NetDb searches that must have finished before a failure ratio is trusted. Below this the ratio is noise: one failed lookup on a quiet router would otherwise re-arm every search budget in the group.- See Also:
SEARCH_FAIL_RATIO
static final double SEARCH_FAIL_RATIOOutgoing-search failure ratio above which no NetDb search budget may be tightened, and above which the budgets grow instead.- See Also:
MAX_CONCURRENT_IDLE_TARGET
static final int MAX_CONCURRENT_IDLE_TARGETIdle target for the NetDB max concurrent searches param.Mirrors the untuned default in
IterativeSearchJob(16, or 4 on a slow router). A quiet router settles here rather than at the hardmin, so the router keeps enough search capacity to resolve a cold destination without ramping up from the floor one slot per cycle.- See Also:
HEALTHY_CYCLES_TO_TIGHTEN
private static final int HEALTHY_CYCLES_TO_TIGHTENConsecutive cycles with ample peer supply required before the activity window tightens again. A single ample reading must not withdraw relief from pools that are still refilling, so the window holds for this many cycles rather than snapping to the floor immediately.- See Also:
MIN_TEST_SAMPLES
static final int MIN_TEST_SAMPLESCompleted tunnel test rounds needed before a failure ratio is trusted.- See Also:
TEST_FAIL_RATIO
static final double TEST_FAIL_RATIOTunnel-test failure ratio above which the test schedule may not be relaxed.- See Also:
STALL_TIMEOUT_FLOOR_MS
static final int STALL_TIMEOUT_FLOOR_MSFloor and default for the I/O stall window, ms. Sized for a bulk transfer: a large download over a congested path routinely pauses longer than a few seconds between copy steps, and a window shorter than that aborts healthy transfers rather than freeing threads.- See Also:
SERVER_BACKLOG_IDLE_FLOOR
static final int SERVER_BACKLOG_IDLE_FLOORIdle autotune target for the I2PTunnel server handler queue capacity. A quiet server settles here rather than at the hardmin, so a healthily sized burst buffer is always retained: the live value can never collapse to the 16-hard-floor under a merely-quiet (60s-avgqueueDepth) monitoring window.- See Also:
STARTUP_GRACE_MS
private static final long STARTUP_GRACE_MSMinimum uptime (ms) before system health scoring begins. During this window, getScore() returns NaN (UI shows "not yet available") and subsystem rings show gray "collecting" per their own min-event checks.- See Also:
Constructor Details
Tuner
The tuner with all tunable parameters.- Parameters:
ctx- the router context
Method Details
sampleStageCpu
private void sampleStageCpu()Sample per-thread CPU time per stage, once per fast tune cycle (5s).stageFor
Stage a thread belongs to, by name. First match inCPU_STAGESorder wins, and it is the prefix, not the full name, that names the stat and theStageCpuentry.- Parameters:
threadName- a live thread's name, never null- Returns:
- index into
CPU_STAGES, orTuner.ThreadCpuTable.NO_STAGE
resolveThreadNames
private void resolveThreadNames(long[] ids, int count) Read the names of threads this cycle has not classified yet.Caching a thread id's stage is sound because a running thread is never renamed: every
setNamecall in the router sits inside a thread factory, before the thread starts. An id whose CPU counter ran backwards is a reused id and is reset toTuner.ThreadCpuTable.UNRESOLVEDbysampleStageCpu(), so its name is read again rather than trusted.- Parameters:
ids- thread ids sampled this cycle, in any order; ids below 1 are ignoredcount- how many entries at the start of ids are valid, never more than ids.length
stageGrowth
Thread growth step for a CPU-saturated stage: +1 at >= 75% of a core sustained over two cycles, +2 at >= 150%, +3 at >= 300%. Severe saturation (>= 200% total) triggers immediately after a single cycle.- Parameters:
stage- aCPU_STAGESprefix- Returns:
- 0 when not saturated, else the number of threads to add
cpuHeadroom
private boolean cpuHeadroom()Whether the JVM has CPU headroom to add worker threads. Gated on 60s-smoothed process load: growth is allowed while at least one full core of capacity remains unused, so 1-core machines never grow.- Returns:
- true if at least one core of capacity is free
peerScaledCap
Cap pool size by active transport connections: one extra thread above the minimum perdivisorconnections. Bounds worst-case thread count without creating threads on its own — demand signals drive actual growth.- Parameters:
t- the transport, or null if not running (no constraint)min- the pool minimummax- the pool maximumdivisor- connections per extra thread- Returns:
- the effective cap
getUDPTransport
UDP transport, or null if not available.- Returns:
- the UDP transport or null
getNTCPTransport
NTCP transport, or null if not available.- Returns:
- the NTCP transport or null
tunedOr
static int tunedOr(int tuned, int fallback) The value a param last applied, or the config-or-default reading when nothing has been applied yet.Some params push a value into a component that offers no reading back - the CoDel queues hold theirs per instance, with no static getter. Those params' getRuntimeValue() used to read a config property the tuning path never writes, so a successful apply was indistinguishable from a setter that refused to move, and the cycle re-applied and re-warned forever. They record what they applied and read it back through here instead.
- Parameters:
tuned- the last applied value, or -1 when nothing has been appliedfallback- the config-or-default reading to use until then- Returns:
- the applied value if there is one, otherwise fallback
setMaxDispatchAgeMs
static void setMaxDispatchAgeMs(int value) Records the tuned dispatch age. The startup tunables, the auto-tuner's apply and the console override all write it here, so the verify and the transport's GetBidsJob read one value.- Parameters:
value- the max time in ms a message may sit in the outbound dispatch queue
setHandlerThreadPriority
static void setHandlerThreadPriority(int priority) Records the tuned I/O handler thread priority, so the verify andgetHandlerThreadPriority()read one value.- Parameters:
priority- the thread priority to apply, inThreadscale
getHandlerThreadPriority
public static int getHandlerThreadPriority()The handler thread priority.- Returns:
- the target priority for I/O handler threads
adjustHandlerPriority
public static void adjustHandlerPriority()Adjust the current thread's priority to the target handler priority. Call this from handler thread main loops — lightweight volatile read. Critical threads (watchdog, event pumper, job queue) should NOT call this.getMaxDispatchAgeMs
public static int getMaxDispatchAgeMs()The max dispatch age in ms.- Returns:
- the age, in ms
getInternalQueueSize
public static int getInternalQueueSize()The I2CP internal queue size.- Returns:
- the current I2CP internal queue size
getTestClientBudget
public static int getTestClientBudget()The test client budget.- Returns:
- tunnels tested concurrently per client pool (default 256)
getTestRetestBackoff
public static int getTestRetestBackoff()The test retest backoff.- Returns:
- retest delay multiplier as a percent, 100 = no backoff, up to 800
getBuildFailureBuffer
public static int getBuildFailureBuffer()The build failure buffer.- Returns:
- extra tunnels built beyond target per pool, 0-10
setInternalQueueSize
public static void setInternalQueueSize(int size) The I2CP internal queue size, clamped to a sane range (called by Tuner).- Parameters:
size- the queue size
internalQueueSizeMin
static int internalQueueSizeMin()The smallest queue sizesetInternalQueueSize(int)accepts.- Returns:
- half the platform default, in messages
internalQueueSizeMax
static int internalQueueSizeMax()The largest queue sizesetInternalQueueSize(int)accepts.- Returns:
- four times the platform default, in messages
memoryDerivedInitMsgMax
private static int memoryDerivedInitMsgMax()Memory-derived ceiling for per-peer initial concurrent messages: ~1 message per 8MB of max heap, clamped to a sane band. Mirrors the scaling used byTuner.MaxConcurrentMessagesParamso the initial value can never exceed what the JVM can reasonably hold.- Returns:
- one initial concurrent message per 8 MB of max heap, clamped to [64, 1024]
initConcurrentMsgsMax
static int initConcurrentMsgsMax()The ceiling the tuner may propose for udp.peer.initConcurrentMsgs.Capped by what
PeerState.setInitConcurrentMsgs(int)itself accepts: a target above that is rewritten back to the current value and then reads forever as a param that will not take effect.- Returns:
- the memory-derived ceiling, never past the setter's own 256
memoryDerivedClientThreadMax
private static int memoryDerivedClientThreadMax()Memory-derived ceiling for the I2PTunnel client worker pool and its admission gate. A thread-per-connectionSynchronousQueuepool holds one OS thread (JVM stack + framing) per admitted connection, so the pool (and the gate that floors it) must never be allowed past a thread count the max heap can plausibly host. ~1 thread per 2MB of max heap, floored at 256 so even a constrained router keeps a sane baseline, clamped to the 16384 hard cap. Mirrors the memory scaling pattern ofmemoryDerivedInitMsgMax().- Returns:
- the memory-feasible thread ceiling for the client proxy pool
mlkemPreCalcMax
static int mlkemPreCalcMax()The ceiling the tuner may propose for crypto.mlkem.precalcMin.The factory caps the pool at its byte budget rather than at its hard cap, so a floor above that is rewritten back on the way in and the param looks stuck.
- Returns:
- the declared ceiling, never past
MLKEMKeyFactory.getByteCap()
scaleForSystem
static int scaleForSystem(int base, int hardMin, int hardMax) Compute a system-scaled value: base * factor, bounded by min and max. Factor is max(memFactor, coreFactor), halved for slow systems.- Parameters:
base- unscaled value for the detected hardwarehardMin- floor the result is clamped tohardMax- ceiling the result is clamped to- Returns:
- the scaled value, constrained to [hardMin, hardMax]
getMemoryPressure
public static double getMemoryPressure()Returns current heap memory pressure as a ratio from 0.0 (no pressure) to 1.0 (heap full). Used to scale cache sizes and buffer pools.Public because it is the router-wide reading for this signal, and tunables outside this class size themselves against it too.
- Returns:
- heap pressure, 0.0 to 1.0
applyStartupTunables
Apply the configured values at startup, clamped to the tunable ranges enforced by MaxDispatchAgeParam / HandlerThreadPriorityParam. These fields feedadjustHandlerPriority()and GetBidsJob before the tunable machinery takes over; an out-of-range priority would make setPriority() throw in every handler loop, and a negative dispatch age is nonsense. Kept in a static method (called once from the constructor) so the statics are not written from instance construction.- Parameters:
autotune- the shared autotune config used as the BaseParam fallbackctx- the router context
purgeOldTunerProps
Remove old tuner.* properties fromrouter.configthat were written there before autotune.config was introduced. Only purges keys we know about (tuner.PARAM.value/default/min/max/step), leaves user-configured props alone.- Parameters:
ctx- the context whose router.config is scanned and rewritten
timeReached
public void timeReached()Expire entries and reschedule the next check.- Specified by:
timeReachedin classSimpleTimer2.TimedEvent
fastCycle
private void fastCycle(long now) Fast 5s cycle: transport worker thread pools react to CPU saturation.- Parameters:
now- current time in ms, compared with the last fast cycle
slowCycle
private void slowCycle(long now) Slow 15s cycle: system health, param updates, crypto pools and derived values.- Parameters:
now- current time in ms, compared with the last slow cycle
updateSlowParams
Update all non-fast params on the 15s cycle.- Parameters:
health- the system health snapshot computed for this cycle and handed to each param
noteParamUpdateFailure
Record that a param's tuning cycle threw.A throwing param is invisible: the cycle handler swallows it, the param never updates, and nothing anywhere shows it as dead. That is how
i2p.tunnel.build.requestTimeoutsat permanently inert — it threw on every cycle for the life of the router and the only evidence was a repeated stack trace in a log file nobody reads.Two things make it visible now: a counter on
tuner.paramUpdateFailedthat appears on/stats, and a per-param rate limit so a permanently broken param logs once with its trace and then stays quiet instead of re-printing every 15 seconds. Both are needed — the counter says a param is dead, the single trace says why.- Parameters:
param- the param that failede- the exception the cycle handler swallowed
refreshCryptoPoolSizes
private void refreshCryptoPoolSizes()Refresh XDH, EDH and MLKEM precomputation pool sizes.computeTuningValues
private void computeTuningValues()Compute derived tuning values from subsystem scores and job queue stats. Updates static volatile fields read byTestJoband TunnelPool. Called once per tune cycle fromtimeReached().getBuildSuccessRate
private double getBuildSuccessRate()Build success rate from hourly stats (0.0-1.0).- Returns:
- success rate or NaN if insufficient data
getJobQueuePressure
private double getJobQueuePressure()Job queue pressure score (0.0 = idle, 1.0 = severe). Combines jobLag, readyJobs, and droppedJobs into a single metric.- Returns:
- the job queue pressure
getAvgStat
60s rolling average of the named stat, or NaN if unavailable.- Parameters:
name- the statManager rate name to read- Returns:
- 60s rolling average of a stat, or NaN if unavailable
getHourlyAvgStat
Hourly rolling average of the named stat, or NaN if unavailable.- Parameters:
name- the rate whose hourly average is wanted- Returns:
- hourly rolling average of a stat, or NaN if unavailable
getEventCountStat
Event count in the last 60s for the named stat, or NaN if unavailable.- Parameters:
name- the rate whose event count is wanted- Returns:
- event count in the last 60s for a stat, or NaN if unavailable
shutdown
public void shutdown()Flush on shutdown — unconditional write if dirty.getSnapshots
Snapshots of all tunable params.- Returns:
- list of param snapshots
getHealthScore
public double getHealthScore()Latest system health score (0.0 = worst, 1.0 = perfect). Returns NaN if health hasn't been computed yet.- Returns:
- the health score
getSubsystemScores
Per-subsystem health scores computed during the last tuning cycle. Returns a list of SubsystemScore sorted by the SUBSYSTEM_ORDER used on the tuning page.- Returns:
- list of subsystem scores
getAutotune
Shared AutotuneConfig instance. Form handlers should use this (not new AutotuneConfig()) so writes go to the same in-memory instance the Tuner reads from.- Returns:
- the autotune config
getParam
Tunable param by name.- Parameters:
name- the param name- Returns:
- the param or null
setOverride
An override value for a param.- Parameters:
name- the param namevalue- the override value
restoreDefaults
public void restoreDefaults()Restore all params to factory defaults (code-level, not persisted).findStaticMethod
static Method findStaticMethod(Class<?> c, String name, Class<?>... args) throws NoSuchMethodException Find a static method reflectively and make it callable.Two properties of the targets break the obvious lookup, and both fail quietly. The member may be package-private, which
Class.getMethod(String, Class...)does not see at all, and it may sit on a package-private class, which makes even a public member throw IllegalAccessException at invoke time. Streaming'sConnectionOptionsandConnectionare non-public classes with public static members, so a getMethod-based call site took the first branch and a bare invoke took the second: either way the param tuned nothing while its getter handed back a fallback that looked like a live reading.Superclasses are walked because the members are not all declared on the class named at the call site.
- Parameters:
c- the class to searchname- the static method nameargs- the parameter types, empty for a getter- Returns:
- the method, already made accessible
- Throws:
NoSuchMethodException- if no class in the hierarchy declares it
reportReflectorFailure
Report a reflective failure once per method.A getter returning its sentinel is indistinguishable from "unknown" at the call site, where it becomes a plausible-looking fallback, so a swallowed exception here reads downstream as a healthy runtime reading.
- Parameters:
c- the class the call was made againstname- the method that failedt- what went wrong
reflectorFailureKey
shouldReportReflectorFailure
Whether a reflective failure has not been reported yet, claiming the one report it is owed.Every method the tuner calls through reflection is reported the first time it fails and never again, because the call site cannot tell a sentinel apart from a reading: silence after the first failure is what let a whole set of streaming params tune nothing while their getters reported plausible values.
- Parameters:
c- the class the call was made against, null when it is not loadedname- the static method that failed- Returns:
- true the first time this class and method pair fails, false afterwards
callStaticInt
Invoke a static int getter, or returnfallbackif it cannot be reached.- Parameters:
c- the class to call, null when it is not loaded yetname- the static method namefallback- the value to return when the call is unavailable- Returns:
- the value read, or fallback
callStaticSetInt
callStaticDouble
Invoke a static double getter, or returnfallbackif it cannot be reached.- Parameters:
c- the class to call, null when it is not loaded yetname- the static method namefallback- the value to return when the call is unavailable- Returns:
- the value read, or fallback
callStaticLong
Invoke a static long getter, or returnfallbackif it cannot be reached.- Parameters:
c- the class to call, null when it is not loaded yetname- the static method namefallback- the value to return when the call is unavailable- Returns:
- the value read, or fallback
callStaticSetLong
bindReflectorTargets
reflectorTargets
reflectorTargetClass
The class a reflectively-called static is declared on, loaded on demand.A name missing from
_reflectorTargetsis reported the way a failed lookup is, because the call site cannot tell it apart from a module that is simply not installed, and an unbound name tunes nothing.- Parameters:
name- the static method name a reflector is about to call- Returns:
- the declaring class, or null if the name is unbound or its module is not installed
establishTimeoutTarget
static int establishTimeoutTarget(double observed, int min) Target establish timeout for an observed mean handshake duration.Four times the observed mean, floored by the param's own minimum. The floor must come from
minrather than a literal: a hardcoded floor here can sit far below the value the setter enforces, andTuner.BaseParam.clamp(int, int, int)walks the stored value toward the target by_stepwithout consultingminat all - so the stored value would drift down while the runtime value stayed clamped, leaving the console permanently displaying a number the router was not using.- Parameters:
observed- mean successful handshake duration in ms, or NaN if unknownmin- the param's configured minimum- Returns:
- the target in ms, never below
min
streamingLossRate
static double streamingLossRate(double dupEvents, double sendEvents, double rtxPerMille, double rtxBytesPerMille) Compute the recent streaming retransmission rate.The 60-second send/duplicate event counts are the freshest signal and are always available while traffic flows:
PacketQueuesamples both counters once per 16 events, so the ratio of their event counts is the ratio of the populations they sample. Note thatPacketQueueonly counts a transmission as a duplicate oncenumSends > 1, i.e. from the third transmission onward, so a packet resent exactly once contributes no duplicate events — the returned rate is therefore a conservative (under-counting) estimate of retransmission pressure.The closed-stream per-mille ratios are consulted only as a fallback: they are emitted when a stream is torn down, so they are the better signal once available but say nothing during startup or on a long-lived connection.
- Parameters:
dupEvents- 60s event count ofstream.con.sendDuplicateSize, or NaNsendEvents- 60s event count ofstream.con.sendMessageSize, or NaNrtxPerMille-stream.rtxRatio(resends per 1000 messages), or NaNrtxBytesPerMille-stream.rtxRatioBytes(resend bytes per 1000 bytes), or NaN- Returns:
- the retransmission rate clamped to [0.0, 1.0], or NaN when no signal is available at all
streamingLossRate
static double streamingLossRate(double dupEvents, double sendEvents, double dupMessages, double messagesSent, double rtxPerMille, double rtxBytesPerMille) The retransmission rate, preferring the volume-weighted view.The primary path counts observed events. The fallback that replaced it matters when no events were observed in the window but connections did close: the per-mille message ratio is computed per connection as
1000 * dupMessagesSent / messagesSent, so a connection that sent three messages and retransmitted a few records thousands per mille. Averaged over closings it saturates the rate at 1.0 - 100% loss - while the byte-weighted ratio for the same window reads around 2%. The aggregate counters divide the same quantities as sums rather than as an average of ratios, so a three-message connection contributes three to the denominator instead of one vote of equal weight, and the result tracks the volume that was actually carried.- Parameters:
dupEvents- duplicate-send events observed, or NaNsendEvents- send events observed, or NaNdupMessages- duplicate messages sent over the window, or NaNmessagesSent- messages sent over the window, or NaNrtxPerMille- per-connection message retransmit ratio in per-millertxBytesPerMille- byte-weighted retransmit ratio in per-mille- Returns:
- retransmission rate in [0.0, 1.0], or NaN when there is no signal
isStreamLossy
static boolean isStreamLossy(double lossRate) Is the streaming path losing enough that windows should stop growing?- Parameters:
lossRate- rate fromstreamingLossRate(double, double, double, double), possibly NaN- Returns:
- true when the rate is at or above
STREAM_LOSSY_RATE
isStreamLossSevere
static boolean isStreamLossSevere(double lossRate) Is the streaming path losing enough to justify the aggressive shrink?- Parameters:
lossRate- rate fromstreamingLossRate(double, double, double, double), possibly NaN- Returns:
- true when the rate is at or above
STREAM_SEVERE_LOSS_RATE
exponentialClimb
static int exponentialClimb(int current, int step) Clean-path climb for a window-sized parameter:max(step, current / 4).Near the bottom of the range this degenerates to the plain linear
stepso a parameter parked atmincannot take a disproportionate jump, but once it has room the growth is geometric. A healthy window reaches its cap in a handful of cycles instead of one step per cycle for as many cycles as the range spans.- Parameters:
current- current runtime valuestep- tuning step size- Returns:
- the number of units to add this cycle
computeStreamingBdpTarget
static int computeStreamingBdpTarget(double bwSendBps, double rttMs, int messageSize, int min, int max) Compute the streaming congestion-window target from the bandwidth-delay product.How big the pipe can be depends on the real streaming message size: the congestion window counts messages, and each message carries up to
messageSizepayload bytes (default 1730, two tunnel messages). Using the historical 4096 there under-sized the target by ~2.4x and capped sustained throughput below what the network could carry.Returned value is clamped into
[min, max]. Returns-1when there is no usable signal (no bandwidth, or an RTT too short to measure), so the caller can fall back to holding its current value.- Parameters:
bwSendBps- measured average send bandwidth in bytes per secondrttMs- observed streaming RTT in millisecondsmessageSize- streaming payload bytes per window slot, already clamped to a sane rangemin- lower bound for the window (inclusive)max- upper bound for the window (inclusive)- Returns:
- the BDP window target in
[min, max], or -1 if there is no usable signal; never returns a value outside[min, max]
computeStreamingMaxWindowTarget
static int computeStreamingMaxWindowTarget(int current, int min, int max, int step, int defaultValue, double failLifetime, double lossRate, double memPct) Compute the streaming max-window ceiling target for one tuning cycle.The ceiling bounds in-flight messages per connection. It is an upper bound, not a reservation — in-flight data still tracks the ACK-driven congestion window — so a high ceiling never loads a slow path; it only removes a throttle from a fast one.
The former logic additionally shrunk the ceiling whenever the bandwidth-delay product fell below current, but that bandwidth is measured through the current ceiling: on a capped connection a low average is an artifact of the cap, not evidence the cap should drop, and ratcheting the cap down on that artifact produced a down-only spiral (a sub-ceiling bandwidth read-out justifies shrinking the cap, which lowers the read-out further, ...).
The ceiling therefore shrinks ONLY on hard negative signals (duplicate retransmits, gateway congestion, memory pressure) and climbs on a clean path, bounded upward and gated on heap headroom so a large jump can never be the event that pushes the heap into pressure.
The clean-path climb is hyper-responsive: with strong headroom (heap below 40%) it takes four steps per cycle, with moderate headroom (below 50%) two, and at 50% or above it holds until memory frees rather than spending headroom it may need. Combined with the 5s fast cycle, the 8192-message ceiling is reachable from the factory default in four strong cycles (~20s) instead of ~4 slow minutes; every climb is still bounded by
maxand scaled down by health dampening inBaseParam.update().Below the recovery floor (
max(min, defaultValue / 2)) and otherwise healthy, the ceiling climbs two steps toward the factory default; under hard signals it shrinks half a step but never below the recovery floor, so a depression cannot be torn back down to the floor trim. Shrinking is deliberately the fastest way to lose headroom: loss, congestion, or heap above 60% all pull the ceiling back within one (fast) cycle.- Parameters:
current- current ceiling value (Connection#getGlobalMaxWindowSize)min- lower bound for the ceiling (inclusive)max- upper bound for the ceiling (inclusive)step- one tuning step; clean-path climbs use four steps under strong heap headroom and two otherwise, hard-signal shrinks use half a stepdefaultValue- factory default ceiling; the recovery floor is max(min, defaultValue / 2)failLifetime- stattransport.sendMessageFailureLifetimein ms, or NaN if absentlossRate- streaming retransmission rate (0.0-1.0), or NaN if absent; seestreamingLossRate(double, double, double, double)memPct- statjobQueue.memoryUsedPercent, or NaN if absent- Returns:
- the new ceiling target within
[min, max]
maxWindowDecision
static String maxWindowDecision(double failLifetime, double lossRate, double memPct, long dupEvents, long sendEvents, double avgDupSize, double eventRatio) Say why the streaming ceiling took the value it did. Pure, so the wording and the branch it names can be pinned by tests.The ceiling's direction is decided by three gated signals and the branch that shrinks is reached only when one of them is true. Reproducing those gates here rather than instrumenting the branch itself keeps the diagnostic next to the policy it explains, and because it is emitted only when the value actually moves, a run of it reads as a trace of the decisions the router made rather than as a per-cycle dump.
This exists because the two disagreed on a live router: the ceiling spent three quarters of a run stepping down by half a step while every one of the three signals measured below its threshold, and no amount of reading the stats page could say which gate was open. Naming the values alongside the verdict is what makes that visible in the log.
- Parameters:
failLifetime- stattransport.sendMessageFailureLifetimein ms, or NaNlossRate- the driver signal — the true byte-weighted rate, 0.0-1.0, or NaNmemPct- statjobQueue.memoryUsedPercentin %, or NaNdupEvents- resend event count behind the event-count ratio, 0 when unreadsendEvents- send event count behind the event-count ratio, 0 when unreadavgDupSize- mean bytes per retransmitted message, or NaN when unreadeventRatio- event-count ratio, 0.0-1.0, or NaN — observation only, never the driver- Returns:
- the verdict on the first line, then the values behind it on
\n*evidence lines, never null - See Also:
maxWindowDecisionFrom
static String maxWindowDecisionFrom(double failLifetime, double driverRate, double eventRate, double memPct, long dupEvents, long sendEvents, double avgDupSize) Assemble the ceiling's decision reason from its signals, with the two loss rates named so the slot assignment cannot be transposed.This seam exists because
maxWindowDecision(double, double, double, long, long, double, double)takes seven positional parameters and the two loss rates are the pair most easily swapped. A previous commit passed the event-count ratio where the true byte-weighted rate belonged, so the line reported a verdict the ceiling had not taken — and no test caught it, because every test calledmaxWindowDecisiondirectly with the driver already in the driver slot. The function was right and the wiring was wrong. Naming the two rates here states the mapping in one place and makes it directly testable.- Parameters:
failLifetime- stattransport.sendMessageFailureLifetimein ms, or NaNdriverRate- the true byte-weighted rate that decides — 0.0-1.0, or NaNeventRate- the event-count ratio, reported but not deciding — 0.0-1.0, or NaNmemPct- statjobQueue.memoryUsedPercentin %, or NaNdupEvents- resend event count behindeventRate, 0 when unreadsendEvents- send event count behindeventRate, 0 when unreadavgDupSize- mean bytes per retransmitted message, or NaN when unread- Returns:
- a short clause naming the verdict and the values behind it, never null
ceilingDirection
static int ceilingDirection(double lossRate, int riseShrink, int riseClimb) Which way the ceiling wants to move, with a dead-band between the two directions. Pure, so the band and the confirm count can be pinned without a router.The dead-band is the point. One threshold serving both directions means the signal only has to sit on it, and a small-sample ratio sits on it by construction. Shrinking at the full lossy threshold and climbing at half of it leaves a band where the honest answer is "unchanged", which is what lets the ceiling rest at an intermediate value.
Boundaries: shrink is inclusive at
STREAM_LOSSY_RATEand above; climb is inclusive atSTREAM_LOSSY_RATE * CEILING_CLIMB_FRACTIONand below; the open interval between them is the dead-band. A direction is returned only afterCEILING_CONFIRM_WINDOWSconsecutive windows in that zone — a single reading is one period of a small-sample ratio.- Parameters:
lossRate- event-count retransmission rate 0.0-1.0, or NaNriseShrink- consecutive windows already held in the shrink directionriseClimb- consecutive windows already held in the climb direction- Returns:
- the direction to move, or
CEILING_HOLD
wholePct
A value already expressed in percent, as a whole-percent string.- Parameters:
percent- the value on a 0-100 scale- Returns:
- the rounded percent with a % sign, or "no signal" for NaN
pct
A fraction as a whole-percent string; NaN becomes "no signal".- Parameters:
rate- the fraction on a 0-1 scale- Returns:
- the rate as a whole percent with a % sign
secs
A millisecond duration as a seconds string with one decimal.- Parameters:
ms- the duration in milliseconds- Returns:
- the duration in seconds with one decimal, or "no signal" for NaN
congestionAvoidanceGrowthTarget
static int congestionAvoidanceGrowthTarget(int current, int min, int max, int step, int defaultValue, double buildSuccess, double failLifetime, double lossRate, double lifetimeRTT, double lifetimeWindowSize, double chokeSize, double congWindowSize, double rtxRatio, double rtxRatio5) Compute the target forCongestionAvoidanceGrowthParam.Extracted as a static method for unit testing, matching
maxSlowStartWindow(int, int, int, int, int, double, double, double).- Parameters:
current- current runtime valuemin- minimum allowed valuemax- maximum allowed valuestep- tuning step sizedefaultValue- factory default valuebuildSuccess- tunnel build success rate (0.0-1.0), or NaN if unknownfailLifetime- sendMessageFailureLifetime stat (ms), or NaNlossRate- streaming retransmission rate (0.0-1.0), or NaNlifetimeRTT- completed stream RTT (ms), or NaNlifetimeWindowSize- final send window of closed streams, or NaNchokeSize- choke-depth stat, or NaNcongWindowSize- window size at congestion, or NaNrtxRatio- hourly retransmit ratio (per-mille), or NaNrtxRatio5- 5-minute retransmit ratio (per-mille), or NaN- Returns:
- target value clamped to [min, max]
windowsCongesting
private static boolean windowsCongesting(double congWindowSize) Whether the congestion window at recent congestion events was too small to count as a real congestion signal — the shared "window collapsed" predicate for both streaming growth-factor targets, so the two cannot drift apart.- Parameters:
congWindowSize- window size at congestion stat, or NaN- Returns:
- true if the window at congestion was below 5 messages
slowStartGrowthTarget
static int slowStartGrowthTarget(int current, int min, int max, int step, int defaultValue, double observed, double buildSuccess, double failLifetime, double lossRate, double lifetimeWindowSize, double chokeSize, double congWindowSize, double rtxRatio, double rtxRatio5) Compute the target forSlowStartGrowthParam.Extracted as a static method for unit testing, matching
congestionAvoidanceGrowthTarget(int, int, int, int, int, double, double, double, double, double, double, double, double, double).- Parameters:
current- current runtime valuemin- minimum allowed valuemax- maximum allowed valuestep- tuning step sizedefaultValue- factory default valueobserved- outbound RTT in ms (may be NaN)buildSuccess- tunnel build success rate (0.0-1.0), or NaN if unknownfailLifetime- sendMessageFailureLifetime stat (ms), or NaNlossRate- streaming retransmission rate (0.0-1.0), or NaNlifetimeWindowSize- final send window of closed streams, or NaNchokeSize- choke-depth stat, or NaNcongWindowSize- window size at congestion, or NaNrtxRatio- hourly retransmit ratio (per-mille), or NaNrtxRatio5- 5-minute retransmit ratio (per-mille), or NaN- Returns:
- target value clamped to [min, max]
streamCeilingTarget
static int streamCeilingTarget(double active, int current, int min, int max, int step, boolean refusing, boolean distress) Pure decision logic for the stream ceiling.Demand-following elasticity: raise the ceiling as legitimate demand or cap-refusals appear so locally hosted services and the HTTP proxy keep flowing; only genuine router starvation (
distress) pulls it down, and one step at a time so bursts survive. Malicious floods are handled by the refusal-layer SYN gate and 24h auto-ban, not by crippling this cap.- Parameters:
active- active-stream demand on the last arrival (NaN when idle)current- the current ceiling (never 0 here; caller maps unarmed to max)min- the config floor for the ceilingmax- the config ceiling (top of working range)step- the Tuner step sizerefusing- true when the "dropped for conn limit" guards fired recentlydistress- true only under super-high job-lag WITH congestion/latency- Returns:
- the target ceiling for the streaming override
searchFailureRatio
static double searchFailureRatio(double successes, double failures) Failure ratio of this router's own outgoing NetDb searches.Use the per-search outcome stats:
netDb.successTimeandnetDb.failedTimeeach fire once per completed iterative search inIterativeSearchJob, so their event counts are our successes and our failures.Do not use
netDb.lookupsMatchedornetDb.lookupsMatchedLeaseSetfor this — those are written byHandleDatabaseLookupMessageJobwhen this router ANSWERS another router's lookup. They measure floodfill service, not our own searches, and sit near zero on a non-floodfill, which reads as "every lookup failed" and parks the whole budget group at its floor.- Parameters:
successes- event count ofnetDb.successTime, or NaNfailures- event count ofnetDb.failedTime, or NaN- Returns:
- the failure ratio in [0, 1], or NaN when too few searches completed
outcomeRatio
static double outcomeRatio(double successes, double failures, int minSamples) Failure ratio from per-attempt outcome event counts.Both arguments are event counts of stats that fire once per completed attempt, one on the success path and one on the failure path. A NaN side is read as zero events, so a stat with no traffic in the window does not skew the ratio; it just lowers the sample count.
- Parameters:
successes- event count of the success-path stat, or NaNfailures- event count of the failure-path stat, or NaNminSamples- completed attempts needed before the ratio is trusted- Returns:
- the failure ratio in [0, 1], or NaN when too few attempts completed
perPeerSearchTimeMs
static double perPeerSearchTimeMs(double successTime, double successRetries) Per-peer reply budget implied by a completed successful search.netDb.successTimeis the whole multi-round search andnetDb.successRetriesis (peers queried - 1), so the quotient is the mean cost of one round; doubling it leaves headroom for a slow round without letting the per-peer window run out to the total search cap.- Parameters:
successTime- mean successful search duration in ms, or NaNsuccessRetries- mean additional queries per successful search, or NaN- Returns:
- the per-peer budget in ms, or NaN when there is no signal
nextWindowMultiplier
static int nextWindowMultiplier(int fastPeers, boolean startupGrace, int healthyCycles, int min, int max, int step) Next window multiplier for a tuning cycle, keyed on peer supply.Pure so the policy is testable without a router. The multiplier is the ceiling
TunnelPeerSelector.windowMultiplierFor(int, boolean, int, int)allows for the current fast-peer count, and widening applies in one step because scarcity is not noisy the way a single build-success sample was. Tightening waits forHEALTHY_CYCLES_TO_TIGHTENconsecutive cycles with ample supply, so a momentary dip cannot withdraw relief from a pool that is still refilling.- Parameters:
fastPeers- peers currently classified faststartupGrace- true during the startup grace periodhealthyCycles- consecutive cycles observed with ample peer supplymin- floor, e.g. 1max- ceiling, e.g. 4step- retained for call-site symmetry; the gate is continuous- Returns:
- the multiplier to apply
lookupLimitTarget
static int lookupLimitTarget(int current, int min, int max, int step, boolean lookupsSlow, boolean queueBackedUp, boolean dropsHappening, boolean lookupsFast) Pure starvation-first decision for next-hop lookup concurrency (see PercentLookupLimitParam):- queue full or overflowing while lookups succeed in time → demand exceeds the lid; raise aggressively so queued entries convert instead of expiring as silent build drops
- lookups genuinely slow with nothing queued → netdb overload; less vetting is cheaper
- fast and quiet → more vetting is cheap
- anything else (mixed signals) → hold
Static and package visible for tests.
- Parameters:
current- current percent valuemin- minimum allowed valuemax- maximum allowed valuestep- the adjustment increment, doubled on a backed-up queue and subtracted in full on slow lookupslookupsSlow- true when successful lookups average > 5000msqueueBackedUp- true when the pending-lookup queue averages highdropsHappening- true when queue-full drops occurred recentlylookupsFast- true when successful lookups average < 2000ms- Returns:
- the target percent value, clamped to [min, max]
sendWindowTarget
static int sendWindowTarget(int current, int min, int max, int step, int idleFloor, double observed, double jobLag, double memPressure, double retransmits, double congCWIN, double sendWindow, double rtxRatio, double failsafeCloses, double mtuDecrease) Pure CWIN growth/shrink decision for the max send window, extracted fromMaxSendWindowParam#computeTargetso the branches are unit-testable without a Tuner context.Priority: memory-critical shrink, death-spiral growth, failsafe/MTU shrink, congestion-growth, high-usage growth, stale-ratchet recovery, clean-path pre-ramp, low-usage shrink. The shrink branch never drives the window below the anti-ratchet idle floor (the stable default) — the 64KB floor that previously froze CWIN is only reachable via memory pressure, and growth always wins under congestion. The stale-ratchet recovery climbs any value parked below the idle floor back up (a leftover low ratchet cannot permanently cap the CWIN), and the clean-path pre-ramp climbs a healthy, non-idle window toward the ceiling so a fast pipe is never capped by the transport window.
- Parameters:
current- the current runtime CWIN in bytesmin- hard lower clamp in bytesmax- hard upper clamp in bytesstep- per-cycle adjustment in bytesidleFloor- anti-ratchet floor for idle shrink (max of default and min)observed-udp.avgSendWindowusage (NaN = no data)jobLag-jobQueue.jobLag(NaN = unknown)memPressure- heap pressure ratio 0.0-1.0retransmits-udp.retransmitEventsrecent countcongCWIN-udp.congestionOccurredrecent countsendWindow-udp.avgSendWindowfor collapse detectionrtxRatio-stream.rtxRatiohourly ratiofailsafeCloses-ntcp.failsafeClosesrecent countmtuDecrease-udp.mtuDecreaserecent count- Returns:
- the new CWIN target, already clamped to [min, max]
maxSlowStartWindow
static int maxSlowStartWindow(int current, int min, int max, int step, int factoryDefault, double observed, double failLifetime, double lossRate) Compute the target value forMaxSlowStartWindowParam.This is extracted to a static method for unit testing, matching the pattern of
sendWindowTarget(int, int, int, int, int, double, double, double, double, double, double, double, double, double).Clean-path increases are exponential: a cycle adds
max(step, current / 4), so a healthy cap climbs from the recovery floor tomaxin ~8 cycles (~2 minutes on the 15s slow cycle) instead of one linearstepper cycle (48 cycles, 12 minutes), unlocking slow start's exponential ramp on high-BDP paths much sooner. Nearminthe term degenerates to the linearstep, and shrink branches are untouched: loss still pulls the cap back fast. Health dampening inBaseParam.update()still applies on top.- Parameters:
current- current runtime value of the parametermin- minimum allowed valuemax- maximum allowed valuestep- tuning step sizefactoryDefault- factory default valueobserved- outbound RTT in ms (may be NaN)failLifetime- sendMessageFailureLifetime stat (may be NaN)lossRate- streaming retransmission rate (0.0-1.0), or NaN if absent; seestreamingLossRate(double, double, double, double)- Returns:
- target value clamped to [min, max]
computeServerHandlerThreads
static int computeServerHandlerThreads(int current, int min, int max, double queueDepth, double active, double blockingTime, double jobLag, double synExpire) Pure decision logic for the I2PTunnel server handler pool size. Extracted (package-visible, static) so unit tests can exercise the policy without a liveRouterContext.Signals:
queueDepth— handler tasks waiting for a free pool thread.active— number of handler threads currently busy (i2ptunnel.serverHandler.active). When this pins the pool ceiling while the queue backs up, handlers are starving connections.blockingHandleTimestays low in that case because it only measures the fast local socket connect to the app, not the slow inbound I2P write to the peer, so saturation is the reliable signal.blockingTime— local handler connect time (not the I2P write).jobLag—jobQueue.jobLagas a CPU-pressure veto: never grow a pool on a swap/busy box.synExpire— router-wide SYN accept-queue expire rate percent (stream.con.synExpireRate). A high rate is direct latency evidence: SYNs are expiring in the accept queue because the inbound I2P write is slow, NOT because the handler pool is under-provisioned. Handlers stall on the same slow transport, so adding threads cannot drain faster; aggressive growth here would just pile up more blocked sockets. A high expire rate therefore moderates growth rather than vetoing it.
- Parameters:
current- current pool sizemin- the floor the shrink branch stops atmax- the ceiling every growth branch clamps toqueueDepth- 60s avg ofi2ptunnel.serverHandler.queueDepth, or NaNactive- 60s avg ofi2ptunnel.serverHandler.active, or NaNblockingTime- 60s avg ofi2ptunnel.serverHandler.blockingHandleTime, or NaNjobLag- 60s avg ofjobQueue.jobLag, or NaNsynExpire- 60s avg ofstream.con.synExpireRate(percent), or NaN- Returns:
- the new pool size
computeReceiveWorkers
static int computeReceiveWorkers(int current, int min, int max, double backlogged, double queueDepth, double rtxRatio, double jobLag) Pure decision logic for the Tuner-managed inbound receive-worker default (i2p.streaming.receiveWorkerThreads). Extracted (package-visible, static) so unit tests can exercise the policy without a liveRouterContext.Signals:
backlogged— 60s avg ofstream.receiveBacklogged(producer-blind events on a full receive shard queue), NaN when none.queueDepth— 60s avg ofstream.receiveQueueDepth.rtxRatio— resends per 1000 sends (loss signal), NaN when unknown. Threshold 50 = 5% resends, matching InitialWindowSizeParam.jobLag— 60s avg ofjobQueue.jobLag(CPU pressure), NaN when unknown.
Policy (throughput / latency / reliability):
- Grow by one shard when producers were blind on a full queue this period and the CPU is not pegged — the dispatcher is the bottleneck, so parallelize the inflow (throughput).
- Never shrink while the path is lossy (
rtxRatio > 50): the retransmission burst still needs its dispatch capacity, and shrinking under loss risks re-backlogging a just-relieved queue (reliability). - Slowly reclaim a shard when the queue holds near-empty (
queueDepth <= 0.25) with no growth veto active — a leaner pool keeps the same latency (latency). The probe requires real (non-NaN) observations, so a genuinely idle manager is left alone rather than churned.
- Parameters:
current- current live worker countmin- lower bound (2)max- upper bound (8)backlogged- 60s avg ofstream.receiveBacklogged, or NaNqueueDepth- 60s avg ofstream.receiveQueueDepth, or NaNrtxRatio- resends per 1000 sends, or NaNjobLag- 60s avg ofjobQueue.jobLag(CPU pressure), or NaN- Returns:
- the new worker count, clamped to [min, max]
computeClientMaxConnections
static int computeClientMaxConnections(int current, int min, int max, double shedRate, double activeThreads, int maxRunner) Pure decision logic for the Tuner-managed default client concurrent-connection cap (i2ptunnel.maxConnectionsdefault). Extracted (package-visible, static) so unit tests can exercise the policy without a liveRouterContext.Signals:
shedRate— events/minute ofi2ptunnel.clientConnectionShed. A non-zero rate means the accept loop is closing excess inbound connections (zero bytes) because the cap is too low; this is the primary "raise" signal and the direct observable of the empty-response symptom.activeThreads—i2ptunnel.clientRunner.activeThreads(60s avg of true executor load). When it pins nearmaxRunnerthe executor is saturated, so raising the connection cap would only pile onto a busy pool.current/min/max— the current default (per-tunnel overrides are untouched), floor, and ceiling.
Policy: any shed (admission-gate overflow or executor-full rejection) is the unambiguous overload signal — grow the gate decisively and fast so excess connections are admitted and served instead of dropped as empty responses. The worker pool is floored at this gate in
TunnelControllerGroup.setClientRunnerMax, so a decisive gate rise also pulls the executor ceiling up (no more 256-vs-27 wedge). Near-saturated executor grows moderately; healthy load grows gently; idle decays slowly so a quiet spell does not throw away burst headroom. Rise is deliberately much faster than decay to fully absorb bursts.- Parameters:
current- current default capmin- floor for the default capmax- ceiling for the default capshedRate- 60s avg ofi2ptunnel.clientConnectionShed, or NaNactiveThreads- 60s avg ofi2ptunnel.clientRunner.activeThreads, or NaNmaxRunner- the client executor max pool size (saturation reference point)- Returns:
- the new default cap
computeIOTransferTarget
static int computeIOTransferTarget(int current, double observed, int min, int max) Pure growth policy for the global Server→Client I/O budget. Extracted (package-visible, static) so unit tests can exercise the policy without a live RouterContext.Observed is the busiest pool's utilization — active plus queued transfers divided by that pool's present maximum — not a global sum. Ten pools sharing one budget means the summed pressure divided by the budget reads low even when one pool is saturated; the per-pool ratio makes a saturated pool visible regardless of how quiet the others are. Above 90% the step doubles the current budget so a download burst cannot wait out a +2-per-cycle crawl; between 70–90% growth is still aggressive. Below 20% the budget shrinks by one so a quiet router does not hold threads it does not need.
- Parameters:
current- current runtime budget (clamped to [min,max] first)observed- the busiest pool's utilization rate; NaN or negative leaves the value unchangedmin- inclusive lower boundmax- inclusive upper bound- Returns:
- target budget in [min, max]
nextStallTimeoutMs
static int nextStallTimeoutMs(int current, double observed, int minMs, int maxMs) Next I/O stall window for an observed per-cycle stall delta.Pure, so the policy is testable without a router.
The window only loosens above the floor when transfers are being cut off, and relaxes back to the floor when they are not. It never goes below the floor, and there is deliberately no "no stalls, tighten" branch: a shorter window kills more legitimate transfers, which produces more stall events, which loosens the window, which then tightens again on the next quiet cycle. That limit cycle parked the window at its 5s floor, shorter than any pause in a bulk transfer, and aborted 45MB downloads partway through.
- Parameters:
current- the window in effect, msobserved- stall events in this cycleminMs- the floor in ms; a negative value is raised to 0maxMs- the ceiling in ms; the returned window never exceeds it- Returns:
- the next window, ms
computeServerBacklogQueueCapacity
static int computeServerBacklogQueueCapacity(int current, int min, int max, double queueDepth, double jobLag) Pure decision logic for the I2PTunnel server handler queue capacity. Extracted (package-visible, static) so unit tests can exercise the policy without a liveRouterContext.The queue is the admission buffer between the accept loop and the handler pool.
queueDepth(60s avg) is how deeply it is actually used. Growing it widens the burst window; shrinking it bounds memory (each queued task holds anI2PSocket).Signals:
queueDepth— handler tasks waiting for a free pool thread.jobLag—jobQueue.jobLagas a CPU-pressure veto: never grow the buffer on a swap/busy box.
Idle handling converges the buffer to
SERVER_BACKLOG_IDLE_FLOORrather than tomin: a too-large buffer shrinks, a too-small one grows, so a quiet server retains a burst buffer and can never be parked at the hard 16 floor.minremains only the external setter clamp.- Parameters:
current- current queue capacitymin- the setter's lower clamp, unused here; the idle floor sets the low end insteadmax- the ceiling every growth branch clamps toqueueDepth- 60s avg ofi2ptunnel.serverHandler.queueDepth, or NaNjobLag- 60s avg ofjobQueue.jobLag, or NaN- Returns:
- the new queue capacity
clientRunnerMaxMin
static int clientRunnerMaxMin()The floor the tuner may propose for i2ptunnel.clientRunner.max.TunnelControllerGroup.setClientRunnerMaxfloors the pool at the tunnel's admission gate,clientDefaultMaxConnections, whose own floor isI2PTunnelClientBase.DEFAULT_MAX_CONNECTIONS. A target below the gate is rewritten back on the way in, so the tuner must never offer one. The floor is hardcoded here because the router module cannot reference the i2ptunnel constant;TunerCalleeBoundsTestpins it against the live value.- Returns:
- the lowest client runner pool size the setter will accept
isHealthyBuildRate
static boolean isHealthyBuildRate(double observed) Whether a build success rate counts as healthy for a tuning walk.Four params gate their walk on
observed > 80: the first-hop cooldown, first-hop threshold, stale-build threshold and concurrency throttle. This router runs 70-77% success, so all four saw "not healthy" permanently while their paired timeoutsHigh condition was also permanently true, and each walked to one of its bounds and stayed there -- the cooldown pinned at its 600s ceiling while the concurrency throttle sat at its 50% maximum.80% is a healthy-I2P target, not this network's operating point, so using it as a walk gate means the gate never opens. 50% still separates the broken case (single digits) from the working one without demanding a target this network cannot reach.
- Parameters:
observed- build success rate as a fraction, or NaN when unavailable- Returns:
- true when the rate is at or above the walk's healthy bound