- Direct Known Subclasses:
AliasedTunnelPool
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate classEvent to perform deferred LeaseSet refresh after throttle window expires.(package private) static classAlways build a LeaseSet with Leases in sorted order, so thatLeaseSet.equals(Object)and lease-by-lease equals() always work.private classRepublishes the lease set after a delay.private static classSelected tunnels for a LeaseSet build: the quality-sorted GOOD (or fallback UNTESTED) tunnels and the single best zero-hop tunnel.(package private) static final classWhy a pool could not supply a tunnel, as counts rather than prose.private static classResult of a pool scan: the chosen tunnel (or null), the backlogged fallback candidate, and the recorded last-resort tunnel.(package private) static enumWhy a tunnel's local transport session was lost.private static classFind the tunnel with the far-end that is XOR-closest to a given hash.private static final classCounts of tunnel states gathered in one sweep of the pool.Field Summary
FieldsModifier and TypeFieldDescriptionprivate longRolling average test duration for this pool, ms; 0 until the first completion is recorded.private doubleTest timeout multiplier for this pool, 1.0 until adapted.protected booleanWhether this pool is running.private final RateStat[]Holds the average bandwidth stat once registered.private final RateStat[]Holds the build success rate stat once registered.private LeaseSetMinimum interval between LeaseSet builds (matches getRefreshThrottle(_context)).private final AtomicIntegerBuild timeouts in a row, for stress detection.private intDynamic pool scaling: when a pool repeatedly collapses (EMERGENCY fires), increase the effective tunnel count.protected final RouterContextThe router context.private final AtomicBooleanReentrancy guard for ensureSufficientTunnels().private booleanTrue once this pool has ever reported a non-zero healthy count.private final RateStat[]Cached rate-stat handles so per-build lookups skip the StatManager map.private final longWhen the pool was first installed.private booleanTrue after first LeaseSet with at least one lease was built (not necessarily GOOD).private booleanTrue when last built LeaseSet had fewer leases than wanted — bypass cache.private final List<PooledTunnelCreatorConfig> Configs with builds in flight, guarded by the manager.private longWhen the last condemn warning was logged.private longLast time ensureSufficientTunnels() started a batch of builds.private longWhen the last emergency build pass ran.private longThrottle for ensureSufficientTunnels hot path (per pool).private longLast time buildFallback() logged its zero-hop-refusal warning, to rate-limit it.private longWhen the last last-resort tunnel log was written.private longTimestamp of the last successful LeaseSet build.private longTrack last proactive LeaseSet publish time for rate limiting.private longLifetime total at the last rate update.private longWhen the last no-tunnels warning was logged.private longMinimum interval between pre-build triggers per pool.private longEarliest lease end time of the last published LeaseSet.private longWhen the pool's rates were last recomputed.private longThrottle refresh — publish at most once per throttle window.private longLast time a tunnel was removed from this pool.private longWhen the last selectPeers failure warning was logged.private longRate-limit stamps for the data-phase failure / condemnation log lines.private longLast time a lost-session cause was sampled.private longWhen this pool last reported a materially short LeaseSetprivate intLast healthy count emitted in a thin-pool warning, or -1 before any.private booleanSet when a lease set republish is queued.private intLease-material target override, or 0 when none is active.private longTotal lifetime of tunnels this pool has used.protected final LogThe log.private final TunnelPoolManagerThe manager that owns this pool.private intLive tunnels whose lease is inside the pre-build window, published by the last sweep soBuildExecutor's dispatch ordering can read it without taking the pool lock.private longWall-clock guard for the zombie warning; seeZOMBIE_LOG_INTERVAL_MS.private TunnelPoolThe opposite-direction pool for the same destination.private final TunnelPeerSelectorSelector choosing peers for this pool's tunnels.private final AtomicBooleanTrack if a deferred refresh is already scheduled.private longLast time this pool dispatched a pre-emergency burst, or 0 if it never has.private final StringStat name prefix for this pool's rates.private final ObjectGuards the window rotation; one lock per build completion.Tunnels added recently, by id, for duplicate suppression.private final RateStat[]Holds the rejected-build stat once registered.private TunnelPoolSettingsThe pool's current settings.private longWhen the pool was started.private final RateStat[]Holds the successful-build stat once registered.private final List<TunnelInfo> The pool's live tunnels.private final ReentrantLockLock guarding the tunnel list.private final AtomicLongBuild attempts in the current stats window.private longWhen the current stats window began.private final AtomicLongBuild timeouts in the current stats window.private static final longFixed (tumbling) window of build outcomes for the per-pool timeout rate that scales the pre-build window — the counters reset to zero whenever the window rotates, seetimeoutRate(long, long)andcomputePreBuildWindowMs(double).private static final intIf less than one success in this many, reduce length (exploratory only).private static final intBuild tries override for the second consecutive failure.private static final intBuild tries override for a client pool's first failure.private static final intBuild tries override for a client pool's second failure.(package private) static final longDefault early expiration time for pruned tunnels (120 seconds).private static final longShorter deficit-build spacing while collapsed (zero healthy-safe tunnels, or <= 1 with nothing in flight).private static final longIntermediate deficit-build spacing while the LeaseSet is incomplete but the pool still has usable tunnels (not collapsed).private static final longMinimum interval between deficit-build batches on a non-collapsed pool.private static final longShorter cooldown during total collapse — when a pool has zero usable AND zero untested tunnels, a 30s cooldown leaves the pool stranded for too long.private static final longAn emergency build pass is followed by a 30 second wait.private static final longFloor between ensureSufficientTunnels() runs when the pool is collapsed (usable tunnels <= 1).private static final longIntermediate ensure gate while the published LeaseSet is incomplete (missing required leases) but the pool is not fully collapsed.private static final longNormal minimum interval between ensureSufficientTunnels() runs for a healthy or partially-degraded pool (usable tunnels > 1).private static final Comparator<TunnelInfo> Sorts tunnel infos by expiration, soonest first.private static final longHow recently a transport must have closed an idle session for the loss to be attributed to this router.(package private) static final longRolling average test duration below which the test window is narrowed.private static final longA tunnel counts as in use when it has carried verified traffic within this window.(package private) static final longMinimum remaining life for a stale UNTESTED tunnel to be worth one last-chance test before pruning: a test needs a minute of runway for the request/reply round trip (plus the test itself) before the tunnel expires anyway.private static final longTunnels must have at least this much remaining life to be published.private static final longLease end is set this long before the tunnel expires, so peers re-fetch the new LeaseSet while the gateway still routes instead of racing tunnel death.private static final StringLegacy alias forPROP_PRUNE_EARLY_EXPIRY, read only as a fallback.private static final longHow long a local transport session must stay missing before the tunnel is dropped rather than merely excluded from the health counts.(package private) static final doubleHard ceiling on the adaptive test timeout multiplier.(package private) static final intHard cap on concurrent builds per pool direction in normal operation.private static final intMost tunnels an emergency pass may add.(package private) static final intAbsolute ceiling on builds in flight per pool and direction, applied to every build path including the pre-emergency burst.(package private) static final intCap on last-chance tests enqueued per sweep pass — the global test caps still apply, but an explicit small bound keeps a fully-UNTESTED pool from flooding the first-test buffer in one ensure cycle.(package private) static final longCap on the failure-scaled pre-build window — beyond this, starting earlier would keep nearly the whole pool's lifetime in build mode.(package private) static final longHard ceiling ongetTunnelLifetime(RouterContext): the standard 10m lifetime plus the 1m grace period other routers afford.private static final longPre-build passes are at least 1 minute apart.(package private) static final longBase pre-build window: start replacements this long before existing tunnels expire so fresh builds (10-40s) complete before the old tunnels die.(package private) static final intBuilds started in one pre-emergency burst.private static final longHow long a pre-emergency burst keeps its in-flight builds exempt from the global dispatch budget.(package private) static final doublePre-emergency threshold: fraction of target below which builds start immediately.private static final StringConfig key for the early tunnel expiry margin.private static final longNon-published tunnels with more remaining life than this are fresh — never pruned.private static final longNon-published tunnels with less remaining life than this are pruned at publication.(package private) static final Comparator<TunnelInfo> Best tunnels first for LeaseSet publication: freshest (longest remaining life) so the lease survives floodfill propagation, then lowest average latency, then fewest consecutive failures.private static final longTrack recently-added tunnel IDs to prevent duplicates.private static final longSilent in the first 3 minutes of uptime: peers are still being discovered.private static final longAt most one selectPeers-failure WARN per interval per pool.private static final longMinimum gap between data-phase failure log lines, per pool.(package private) static final doublePriority weight for service pool builds.private static final longInterval between lost-session attribution samples.private static final longMinimum gap between repeated "not enough leases" reports from one pool.(package private) static final longRolling average test duration above which the test window is widened.(package private) static final intSoft-failure count at which a still-usable tunnel is treated as degraded for ensure-throttle and deficit purposes.(package private) static final intHigher removal bar for soft best-effort send timeouts (status 3).(package private) static final intConsecutive build timeouts at which the spare grows to two tunnels.(package private) static final intConsecutive build timeouts at which the pool starts holding one spare tunnel above target (seecomputeDifficultySpare(int)).private static final intExtra tunnels maintained while the pool is struggling, so LeaseSet publication always has fresh candidates instead of re-signing the same aging leases.private static final longMinimum gap between repeated "health excludes N zombie tunnels" warnings from one pool.(package private) static final StringRate stat recording how many tunnelsgetHealthyTunnelCount()dropped for want of a transport session to their local hop.(package private) static final StringRate stat recording how many tunnelsgetUsableTunnelCount()dropped for the same reason.Constructor Summary
ConstructorsConstructorDescriptionTunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel) Tunnel pool.Method Summary
Modifier and TypeMethodDescriptionprivate voidaddDegradedFallbackLease(TreeSet<Lease> leases, List<TunnelInfo> tunnels, boolean isServerPool) Emergency fallback: when the LeaseSet has no leases but one has been published before, add the best degraded tunnel so clients don't lose the destination.private booleanaddOrReplaceTunnel(TunnelInfo info, TunnelId gatewayId, long now, int usable, int maxUsable, int target) Add the tunnel unless the pool is at capacity; at capacity try replacing a non-GOOD tunnel instead of dropping the new build.private static voidCollect a tunnel for the stranded re-offer, bounded byTestJob.MAX_STRANDED_OFFERS_PER_SWEEPper sweep.protected voidaddTunnel(TunnelInfo info) Add a tunnel to the pool.(package private) static booleanalreadyCondemned(boolean isDead, TunnelTestStatus status) Whether a failure report is a repeat of a condemnation already made.private voidAnalyze tunnel failure patterns to identify root causes and suggest recovery strategies.private booleanapplyLengthOverride(int len, int minLen, int th1, int th2, long tot, long sc) Shorten the override length based on the ten-minute success rate.private java.util.function.Predicate<TunnelInfo> Backlog predicate matching the onescanPoolForTunnel(int, long, boolean, TunnelInfo)applies.private voidblameSinglePeer(TunnelInfo cfg, Hash blamePeer) Apply the whole failure penalty to one peer: profile failure, the selection cooldown, tier demotion, and a priority retest so a persistently failing hop is not re-selected on the next generation.(package private) booleanBuild bootstrap tunnels for exploratory pools during cold start.(package private) voidbuildComplete(PooledTunnelCreatorConfig cfg, BuildExecutor.Result result) Remove from the _inprogress list and call addTunnel() if result is SUCCESS.private voidbuildDeficitReplacements(TunnelPool.TunnelStats stats, int target, int effectiveTarget, long now) Proactive replacement: when safeActive is below effectiveTarget and tunnels are expiring within the pre-build window (or the LeaseSet is incomplete), build the deficit now so replacements are ready before the old tunnels die — preventing synchronized expiry cascades.(package private) booleanThis will build a fallback (zero-hop) tunnel ONLY if this pool is exploratory, or the settings allow it.private LeaseBuild a Lease from a single tunnel's gateway.buildLeases(List<TunnelInfo> goodTunnels, TunnelInfo zeroHopTunnel) Assemble the Lease objects for the selected tunnels: the single zero-hop lease (if any), then the quality-sorted GOOD tunnels.private LeaseSetbuildNewLeaseSetFromTunnels(List<TunnelInfo> tunnels, boolean isServerPool, boolean rotateForPropagation, List<TunnelInfo> rotatedOut) Build a LeaseSet from a list of tunnels.private voidbuildReplacementTunnels(int needed, boolean bypassPacing) Build the requested number of replacement tunnels, skipping any configuration failures.cancelExcessInProgress(int maxAllowed) Cancel excess in-progress tunnel builds to stay within budget.(package private) static TunnelPool.PoolStateclassifyPoolState(List<TunnelInfo> tunnels, long now, java.util.function.Predicate<TunnelInfo> backlogged) Count the pool by selection outcome.(package private) static TunnelPool.SessionLossclassifySessionLoss(boolean hadSessionAtBuild, boolean establishedNow, boolean peerReachable) Attribute a lost local transport session to our side or the peer's.voidClear FAILING/FAILED flags on tunnels that have provably carried real traffic recently.voidDrop any lease-material prune override.private voidClear the missing-session timestamp when the session is back, so a tunnel that reconnects does not carry a duration towards its next loss.private TunnelPool.LeaseTunnelscollectLeaseTunnels(List<TunnelInfo> tunnels, long expireAfter, boolean hasGoodTunnel) Select the tunnels that qualify for a LeaseSet: GOOD tunnels (or UNTESTED fallback when no GOOD exists.private List<TunnelInfo> collectNonPublishedTunnels(Set<Hash> publishedGateways, int latencyThreshold, long now) Scan for GOOD tunnels not included in the published LeaseSet.collectPeerForPriorityTest(List<Hash> peersToTest, Hash peer, int peersInTunnel, long startupTime) Collect a peer for priority testing, capped at 3 distinct peers.collectPublishedGateways(LeaseSet publishedLS) Gateway hashes already used by the published lease set.private List<TunnelInfo> collectTrafficProvenUntested(List<TunnelInfo> tunnels, long expireAfter) UNTESTED tunnels that have verified delivering real inbound data recently.private static intLatency ascending, tunnels with no measurements sort last.private booleancomputeAndApplyLengthOverride(int len) Compute the ten-minute build success and shorten the override length under stress, using the thresholds and rate stats for this pool type.private intcomputeBlamePct(TunnelInfo cfg, int i, int len, int start, int consecutiveFailures) Compute the blame percentage for a peer in a failed tunnel.(package private) static intcomputeDeficit(int healthySafe, int safeActive, int softDegraded, int failingCount, int untestedCount, int target, int effectiveTarget, int currentInProgress) Pure deficit calculation so unit tests can pin the soft-degraded occupancy rules without a router context.private intcomputeDeficit(TunnelPool.TunnelStats stats, int target, int effectiveTarget, int currentInProgress) Compute the build deficit.(package private) static intcomputeDifficultySpare(int consecutiveBuildTimeouts) Spare tunnels held above target while the pool is having difficulty building.private intcomputeEffectiveTarget(int target) Effective build target: base target plus the dynamic emergency boost, the Tuner's failure buffer, and a reserve.private intcomputeEmergencyBoost(int target) Boost the build target on repeated collapses, capped at MAX_EMERGENCY_BOOST.private intcomputeFreshBuildCap(int target) Cap on concurrent builds for fresh replacement: at least 4, at most 6, scaled up for high targets.(package private) static longcomputeLeaseEndDate(long tunnelExpiration, long now, long maxLeaseMs) Compute the end date advertised for a lease, given the tunnel's own expiration.private intcomputeNonGoodReserve(List<TunnelInfo> toRemove, int goodCount) LS fallback reserve to keep among the pruned candidates.(package private) static longcomputePreBuildWindowMs(double rate) How far ahead of expiry to start building replacements, scaled by how badly this pool's builds are failing: a pool with a high build-timeout rate needs more lead time because each attempt may burn a full timeout (17-30s) before the next one starts.private intcomputePruneThreshold(int target, boolean isServerPool) Prune threshold: the base target, relaxed when build success is poor — killing spare tunnels you can't rebuild just makes pool collapse more likely.private StringDisplay name for the rate stat: "Exploratory tunnels" for exploratory pools, otherwise the destination nickname (HTML-stripped) or the destination's base32 address.(package private) PooledTunnelCreatorConfigThis only sets the peers and creation/expiration times in the configuration.private PooledTunnelCreatorConfigconfigureNewTunnel(boolean forceZeroHop) This only sets the peers and creation/expiration times in the configuration.(package private) PooledTunnelCreatorConfigconfigureNewTunnel(boolean forceZeroHop, Set<Hash> excludeFirstHops) Configure a new tunnel, optionally excluding peers from first-hop selection.private intcountAllHealthyTunnels(long now, long expiryThreshold) Count unexpired tunnels when all of them expire beyond the given threshold.private intcountFreshTunnels(long freshUntil) Count tunnels whose lifetime still covers the lease viability window and which have not failed.private static intSafe tunnels that are not soft-degraded.(package private) static intcountLeaseViableTunnels(List<TunnelInfo> tunnels, List<TunnelInfo> toRemove, long now) How many tunnels could still be advertised right now, on the same barselectLeaseTunnelsapplies, matching the wording of the sibling prune path.private voidcountLiveTunnel(TunnelInfo t, TunnelPool.TunnelStats stats, long preBuildThreshold) Count a live non-UNTESTED tunnel: safe when it outlives the pre-build window, else near expiry.private voidcountLocalHopTransports(List<TunnelInfo> tunnels) Count the local hop's transport for every tunnel in the walk.private voidAttribute one zombie exclusion to this router or to the peer, and count it.private intCount tunnels that are neither FAILED nor FAILING (dead/dying must not block replacements).(package private) intCount the zero-hop fallback tunnels in this pool.private StringAttribute one zombie exclusion to the transport its local hop used.private PooledTunnelCreatorConfigcreateConfig(TunnelPoolSettings settings, List<Hash> peers, long now, long expiration) Build the config with the given peers, endpoint first.private voidcreateRateStatForName(String name) Register the pool's bandwidth rate stat.private voiddecayEmergencyCounter(int safeActive, int effectiveTarget, int target) Decay the collapse counter: reset when stable or empty (a high counter on an empty pool inflates the target and hoards broken tunnels), else decrement by one per cycle once back at the base target.private static StringdescribeHops(TunnelInfo cfg) Name the hops of a failed tunnel so a data-phase failure is attributable.private intRemove tunnels whose local transport session has been gone long enough that keeping them costs more than rebuilding.(package private) voidEnsure the pool has at least target valid tunnels, building replacements proactively when the count drops below target.private voidensureSufficientTunnelsNow(long nowEnsure) Run the ensure body without the throttle gate.(package private) static booleanexceedsRemovalThreshold(int failures) Whether cumulative data-phase and TestJob failures exceed the removal threshold.(package private) static booleanexceedsRemovalThreshold(int failures, boolean soft) Whether the given failure class exceeds its marking bar.static booleanexceedsSoftRemovalThreshold(int failures) Whether a soft failure count crosses the soft marking bar.private longexpirationWithStagger(long now) Expiration for a new tunnel, staggered to prevent all tunnels expiring simultaneously.private voidExpire all zero-hop tunnels in this pool immediately.private voidfail(TunnelInfo cfg) Marks a tunnel as failed and schedules its replacement.private voidfailWithCount(TunnelInfo cfg, int failures) Mark a failing tunnel for retention using the count that triggered the decision.private booleanfastFailTbrTarget(TunnelPoolSettings settings, PooledTunnelCreatorConfig cfg) Fast-fail for outbound tunnels: skip this build cycle when the TBR target (cfg.getPeer(1)) is not connected and not connecting — the TBR is sent directly to this peer via transport.private LeaseSetfinalizeLeaseSet(TreeSet<Lease> leases, int wanted, long now) Builds the lease set from the chosen leases.private TunnelInfofindBestDegradedTunnel(List<TunnelInfo> tunnels, boolean isServerPool) Find the best degraded tunnel from a given list for emergency LS fallback.private TunnelInfofindClosestTunnel(Hash closestTo, boolean avoidZeroHop, long now) Find the non-failed tunnel XOR-closest to the target, so OBEP-IBGW connections keep some locality network-wide.private PooledTunnelCreatorConfigfindPairedInPool(TunnelPool pool, TunnelId pairedGW) Look up the paired tunnel in the given pool.private TunnelInfoFind a tunnel that can be replaced: FAILED, or FAILING for non-server pools.(package private) static TunnelInfofindReusableCandidate(List<TunnelInfo> tunnels, int len, long now, int idx) Scan the tunnel list from the given index for a reusable tunnel.private TunnelInfofindReusableCandidateLocked(int len, long now, int idx) findReusableCandidate(List, int, long, int)with the pool lock held, so the scan sees a stable tunnel list.findReusableTunnel(int len, long now, long cooldownCutoff) Try to reuse an existing tunnel of the right length that is expiring soon, so its peers carry into the new build.private TunnelInfofindReusedPredecessor(TunnelInfo successor) The active tunnel whose peer set matches the given newly built tunnel (matched by exact gateway-first peer-list equality), or null.voidForce a tunnel to fail immediately and trigger a replacement build.static StringformatPoolIdentity(TunnelPoolSettings settings) Format a pool identity for log messages, combining nickname and truncated hash.private intThe tunnel count the pool maintains, never less than 2 per direction regardless of the configured quantity, unless the pool is a ping pool or expressly zero-hop.intCount GOOD and TESTING (non-failed, not expired) tunnels in the pool.(package private) longThis pool's rolling average of completed test durations, used to adapt the test timeout multiplier.(package private) longgetAdaptiveTestTimeout(long baseTimeout) Adapt a base test timeout to this pool's observed test speed.intAverage bandwidth per tunnel in the pool.private doubleGlobal tunnel build success rate as a fraction (0.0-1.0).private doubleThis pool's build-timeout rate over the current window.(package private) static intQuantity override threshold from config or default (12).(package private) intCount of consecutive tunnel build timeouts.(package private) intCurrent difficulty spare for this pool.(package private) intThe tunnel count the pool builds and keeps: the ordinary active-count target, unaffected by any lease surge.private TunnelIdgetGatewayTunnelId(TunnelInfo info) The tunnel ID used to dedupe additions: receive side for inbound pools, send side for outbound.(package private) intUsable tunnels that are not soft-degraded: same filters asgetUsableTunnelCount()plus a floor onTunnelInfo.getSoftFailures().Build and return current LeaseSet from our tunnels.intCount tunnels currently being built.(package private) static longMaximum lease set lease duration from config or default (10 minutes).(package private) static longLeaseSet build minimum interval from config or default (2 minutes).(package private) intThe advertiseable count an in-force surge needs, or 0 when none is.static longA lease counts toward a re-mint while it has at least this much remaining: three minutes covers the re-sign request, floodfill propagation, and initial connection establishment, and a tunnel closer to expiry than this is already inside the pool's proactive replacement window (BuildExecutor starts replacing GOOD tunnels 330s out), so counting it would overstate the pool's health.longTotal lifetime processed bytes for this pool.(package private) static intMax concurrent builds per direction from config or default (6).intTunnels in this pool whose lease falls inside the pre-build window.(package private) TunnelPoolPaired pool (inbound <-> outbound for same destination).(package private) longEarly expiry time for pruned tunnels.(package private) static longEarly expiry time for pruned tunnels, shared by every prune path (pool sweep and Remove Slow) so one pool's tunnels cannot be given a different head start than another's for the same configuration.RateStat name for the bandwidth graph.private RateStatgetRateStat(RateStat[] slot, String name) Get a rate stat, caching the handle so per-build lookups skip the StatManager lookup.(package private) static longRefresh throttle interval from config or default (2 minutes).(package private) static intgetReplacementTunnelBuffer(double buildSuccess) How many usable tunnels above the target the pool may hold before a further build must replace a non-GOOD tunnel instead of being added.Pool settings.(package private) static longStartup suppression period from config or default (5 minutes).intCount tunnels that have been built but not yet passed their first test.Tunnel by its gateway tunnel ID.(package private) static intAverage test latency for a tunnel.static intTunnel lifetime from config or default (11 minutes).TunnelPoolManager that owns this pool.(package private) intCount tunnels that are usable for routing — not expired, not failed, not marked FAILING.intCount valid (non-failed, not expired) tunnels in the pool.private TunnelInfoHandle a pass whose chosen tunnel was null but which recorded a backlogged candidate: warn and fall back to it.private voidHandle the build result: update the consecutive timeout counter and the paired tunnel profile per outcome.private voidhandleEmergencyIfNeeded(TunnelPool.TunnelStats stats, int target) EMERGENCY rebuild: fires only when zero usable tunnels remain (no safe, none expiring, none untested).private voidhandleStaleUntested(Iterator<TunnelInfo> it, TunnelInfo t, TunnelPool.TunnelStats stats, long now, long wallNow, long preBuildThreshold) Handle an UNTESTED tunnel: a tunnel still UNTESTED within the pre-build window (expiring in < 3 min) is stuck — the test queue never reached it (saturated) or it was abandoned after a pool reset.private booleanhasAllPeerRIs(List<Hash> peers) Whether every non-self peer in the list still has a RouterInfo cached locally.private booleanhasGoodTunnel(List<TunnelInfo> tunnels, long expireAfter) Whether any tunnel in the list is GOOD and expiring after the propagation deadline — gates the UNTESTED fallback.private booleanhasInboundGateway(TunnelInfo tunnel) Whether the tunnel has the required inbound gateway and tunnel ID.private booleanhasPeerOnCooldown(List<Hash> peers, long cooldownCutoff) Whether any non-self peer in the list is on cooldown.(package private) static booleanhasValidTunnelsBlockingEmergency(int validCount) Emergency collapse is deferred while valid tunnels remain (soft-degraded or FAILING-but-not-FAILED).(package private) booleanWhether any active tunnel in this pool is a zero-hop fallback while the pool is configured for multi-hop.(package private) voidIncrement consecutive build timeout counter.booleanisAlive()Is this pool running AND either exploratory, or tracked by the client manager? A pool will be alive but not tracked after the client manager removes it but before all the tunnels have expired.private booleanWhether the tunnel's next hop is backlogged, meaning congested.private booleanisBetterDegradedCandidate(TunnelInfo best, TunnelInfo t, int bestFailures, int failures) Whether the tunnel beats the current best: fewer consecutive failures, or equal failures with a later expiration.private booleanisCachedLeaseSetValid(LeaseSet cached, long now) Whether the cached LeaseSet can be reused.private booleanisDeficitBuildNeeded(TunnelPool.TunnelStats stats, int effectiveTarget) Whether a deficit build is warranted: some tunnels expiring soon, none safe, soft-degraded presence below healthy capacity, or an incomplete LeaseSet that needs more GOOD tunnels — but not when the incomplete-LeaseSet trigger is already satisfied by the current healthy good+untested count.(package private) static booleanisDeficitThrottled(long now, long lastBuild, int safeActive, int inProgress, boolean incompleteLeaseSet) Compatibility overload: callers that only have a single usable count (no soft-degraded split) treat it as both healthy and safe.(package private) static booleanisDeficitThrottled(long now, long lastBuild, int healthySafe, int safeActive, int inProgress, boolean incompleteLeaseSet) Whether a deficit-build batch must wait.private booleanCheck if the destination is reachable by looking up its LeaseSet.private booleanisDuplicateInPool(TunnelInfo info, TunnelId gatewayId) Reject re-adds of the same object (buildComplete called twice) and different objects with the same gateway tunnel ID.private booleanisEligibleDegradedCandidate(TunnelInfo t, boolean isServerPool, long now) Whether the tunnel may be replaced as a degraded candidate.private booleanisEligibleForLease(TunnelInfo tunnel, boolean hasGoodTunnel, long expireAfter) Whether the tunnel may be included in the LeaseSet: not failed, GOOD (or UNTESTED when no GOOD exists), and expiring after the propagation deadline.private booleanWhether an emergency build ran within the cooldown window.(package private) static booleanisEmergencyCooldownActive(long elapsedMs, long usableCount, long inProgressCount) Whether the emergency-cooldown window has elapsed.private booleanWhether the pool is in the emergency condition: zero safe, expiring and untested tunnels, no still-valid (soft-degraded / FAILING) tunnels left to ride out, and not a ping pool.(package private) static booleanisEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, boolean incompleteLeaseSet) Whether ensureSufficientTunnels() must wait for this pool.(package private) static booleanisEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, int healthyCount, boolean incompleteLeaseSet) Whether ensureSufficientTunnels() must wait for this pool, using separate usable and healthy counts.(package private) static booleanisEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, int healthyCount, boolean incompleteLeaseSet, long lastRemoval) Whether ensureSufficientTunnels() must wait for this pool, using separate usable and healthy counts and a removal-time signal.(package private) static booleanisEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, int healthyCount, boolean incompleteLeaseSet, long lastRemoval, int inProgress) Whether ensureSufficientTunnels() must wait for this pool, using separate usable and healthy counts, a removal-time signal, and the number of builds in flight.(package private) booleanisExpiringSoon(long now) Check if the LeaseSet is expiring soon (within 1 minute).private booleanFresh builds only apply to live, non-exploratory inbound pools.(package private) static booleanisLastChanceTestable(long expiration, long now, long minLifeMs) Whether a tunnel expiring within the pre-build window still has enough life left for a last-chance test to mean anything.private booleanWhether this tunnel's local path is unusable because we hold no transport session to the peer we actually send through.private booleanisLongEnoughLived(TunnelInfo info, long now) Only keep tunnels that outlive the 60s add window.(package private) static booleanisMateriallyShort(int leases, int wanted) Whether a published LeaseSet is too small to count as a working set.private booleanPaired-pool gate: skip pruning entirely when this inbound pool's opposite outbound pool is below its target.private booleanWhether this is a short-lived ping pool.private booleanSkip while the pool's build executor is in backoff.(package private) static booleanisPreEmergencyBurstLive(long burstAt, long now, long window) Whether this pool dispatched a pre-emergency burst recently enough that its in-flight builds are governed by the per-pool cap instead of the global budget.(package private) booleanisPreEmergencyExempt(long now) Package-visible for the build executor: whether this pool's in-flight builds should bypass the global dispatch budget.private booleanisProtectedFromPruning(TunnelInfo info, boolean isServerPool, long now) A tunnel is protected from pruning if it is a server-pool GOOD or FAILING tunnel, is actively carrying traffic (non-exploratory pools only.private booleanisProtectedInUse(TunnelInfo t, long wallNow) Whether an UNTESTED tunnel expiring within the pre-build window is protected: it recently carried verified traffic.private booleanisPrunableNonGood(TunnelInfo t, boolean isServerPool) True for tunnels that should be pruned: FAILED/FAILING/unknown-status or failed in test.private booleanisPruneCandidate(TunnelInfo t, Set<Hash> publishedGateways, long now, int latencyThreshold) Whether the tunnel should be pruned: near expiry or slower than the latency threshold, not fresh, not UNTESTED, and not published in the current LeaseSet.private booleanisRecentlyAddedDuplicate(TunnelInfo info, TunnelId gatewayId, long now) Check the recent-additions map for a duplicate gateway ID, cleaning stale entries so the map cannot grow unbounded.private booleanisRefreshThrottled(long now) Whether the refresh throttle window is still active.private booleanWhether this is a server (inbound, non-exploratory) pool.(package private) static booleanWhether the tunnel is soft-degraded for ensure/deficit purposes: still in the pool and not FAILING, but soft failures have reachedSOFT_DEGRADED_FOR_ENSURE.(package private) static booleanisSoftSendFailure(int status) Whether the status is a soft best-effort send timeout (3).booleanWhether this pool is struggling to meet its tunnel targets.(package private) static booleanisTrafficProvenUntestedLeaseCandidate(TunnelInfo tunnel, long expireAfter, long now) Whether an inbound UNTESTED tunnel has verified delivering real data recently enough to be published as a lease.private List<TunnelInfo> keepBestReserve(List<TunnelInfo> toRemove, int toPrune) Keep the reserve tunnels with the fewest failures, drop the worsttoPrune.private static longleaseKeyOf(TunnelInfo tunnel) The identity a lease for this tunnel is keyed on: its receive tunnel id.(package private) static List<TunnelInfo> leaseSurgeCandidates(List<TunnelInfo> tunnels, List<TunnelInfo> toRemove, long now) Tunnels a lease surge may schedule for early expiry: GOOD, past the lease admission floor (so they can no longer be advertised), not already being pruned, and not recently active.List of tunnelInfo instances of tunnels currently being built.List of tunnels currently in the pool.private HashThe peer whose transport session this tunnel sends through, or null when there is none.private StringThe transport a tunnel's local hop uses, folded to one of the known names.protected LeaseSetBuild a leaseSet with the required tunnels that aren't about to expire.private voidWarn once per cycle when zombie or stale untested tunnels were removed.private voidlogConfigDebug(PooledTunnelCreatorConfig cfg, List<Hash> peers) Logs a new tunnel's config and peers at debug level.private voidLog the no-tunnels warning, distinguishing an unreachable destination (no LeaseSet found) from a pool that holds nothing usable.private voidlogPeerBlame(Hash peer, int pct, long uptime, long startupTime, int consecutiveFailures) Log the blame assignment for a peer, gated on startup being complete, warn logging being enabled and an active failure streak.private voidlogSelectPeersFailure(TunnelPoolSettings settings) Warn when the peer selector found nothing for a new tunnel, naming the destination.private intmarkExcessTunnels(List<TunnelInfo> toRemove, boolean isServerPool, int goodTarget, long now, int toPrune) Mark excess tunnels for early expiry, down to the prune threshold.private voidmarkFailedTunnelsForEarlyExpiry(List<TunnelInfo> toRemove, int target, long now) Mark completely failed tunnels for early expiry (all pool types).private voidmarkRotatedOut(Lease rotated, List<TunnelInfo> tunnels, List<TunnelInfo> rotatedOut) Record the tunnel matching the rotated lease as rotated out so the build path can exclude it from the next LeaseSet.(package private) static longmaxExpirationStagger(long lifetimeMs) Upper bound for the expiration stagger: keeps lifetime plus stagger under the NetDb's 15-minute future-lease rejection boundary with a 15-second margin, and never exceeds 4 minutes.private voidWhen the paired opposite-direction pool is nearly empty (size <= 1), its tunnels are slow/failed and cleanup may not catch up — nudge the manager to pre-build a replacement now, throttled.(package private) booleanDo we really need more fallbacks? Used to prevent a zillion of them.(package private) static StringnoPeersSelectedLine(String destName, boolean inbound) The entry naming the pool whose peer selection came up empty, so the reason a new tunnel could not be built is visible without a debug log.(package private) voidCalled by TestJob when a server pool tunnel fails a test but is retained.private voidOffer tunnels that have never produced a latency reading.(package private) static intRank a candidate test partner; lower is better.(package private) static booleanpassesScanGates(TunnelInfo info, long now, boolean longTunnelsOnly) Whether a pooled tunnel passes the scan eligibility gates: not failed, not over the consecutive-test-failure cap (test failures are often reply-path problems, not tunnel quality), non-expired, and — on the first pass.The tunnel's peers, endpoint first, the order selectPeers() returns them in.private TunnelInfopickAnyNonExpiredTunnel(long now) Pick any live tunnel from the pool, expired ones excluded.private TunnelInfopickDegradedTunnel(long now) Last-resort scan: accept any non-expired tunnel, even one over the consecutive-failure cap.(package private) voidProactively republish the LeaseSet when all tunnels are healthy (all > 5 min expiry).private voidprocessRemovalStats(List<TunnelInfo> removed) Process removal statistics for removed tunnels.private voidpruneExcessGoodTunnels(List<TunnelInfo> toRemove, int toPrune, long now) Fallback prune for non-server pools: when only GOOD tunnels remain to prune, mark the soonest-expiring GOOD tunnels for early expiry, skipping tunnels actively carrying traffic.intPrune excess tunnels from the pool to stay within budget.private voidpruneForLeaseSurge(List<TunnelInfo> toRemove, int leaseSurge, long now) Schedule early expiry for the least lease-material GOOD tunnels while a lease surge is active.private voidRemove non-GOOD tunnels from the pool when enough GOOD ones remain.private voidpruneNonPublishedTunnels(LeaseSet publishedLS) After publishing a LeaseSet, prune GOOD tunnels that weren't included in the published LeaseSet.private voidpruneOverBudget(List<TunnelInfo> toRemove, boolean isServerPool, long now, int currentSize, int pruneThreshold) When the pool exceeds the prune threshold, mark excess tunnels for early expiry, preferring GOOD tunnels for non-server pools.private voidpruneOverReserve(List<TunnelInfo> toRemove, int goodCount) Prune the non-GOOD tunnels down to the LS fallback reserve, keeping the best (fewest failures), then refresh the published LeaseSet.private static intPrune priority rank — lower prunes first.private voidRemove any zero-hop bootstrap tunnels from the pool.private booleanDoes this pool publish a LeaseSet to the network? Structural: inbound + non-exploratory.private voidBuild and publish the first LeaseSet on startup for server pools so clients can find the destination immediately.private voidpublishLeaseSetIfNeeded(TunnelInfo info, LeaseSet ls, long now) Publish a freshly-built LeaseSet, bypassing the refresh throttle when the pool is nearly empty AND the published LeaseSet is missing or expiring soon.private voidpublishRefreshedLeaseSet(long now) Build the current LeaseSet from the pool and publish it, pruning tunnels that were dropped from the LeaseSet.private LeaseSetBuild a fresh LeaseSet from the surviving tunnels after a removal.private voidrecordBuildOutcome(boolean timedOut, long now) Record one build outcome in the fixed-width rate window.private voidRecord peer cooldown on build failure so bad OBEPs/IBGWs aren't retried.private voidrecordPeerCooldowns(List<Hash> peers, long now) Record a diversity cooldown for every reused peer so selectPeers respects them on the next selection.private voidRecord activity on a pooled tunnel (no-op for non-pooled).(package private) voidNo-op for outbound and exploratory pools.voidrefreshLeaseSet(boolean force) Refresh the LeaseSet, throttled to prevent flooding but not on initial creation.private voidRe-reads the pool's settings from the config.private voidRaise the peer-selection activity window to its ceiling because a pool has no usable tunnels, and publish the new value.private voidRemove a tunnel config from the dispatcher and the expiration queue, in that order.(package private) voidRemove a config that was never actually sent to the network (e.g.private voidremoveRotatedOutTunnel(TunnelInfo rotated) Remove a rotated-out tunnel from the pool.(package private) voidremoveTunnel(TunnelInfo info) Remove a tunnel from the pool.private voidRemove a tunnel from the pool and, if it is a build config, from the dispatcher and the expiry map as well.private voidremoveTunnelsBatch(List<TunnelInfo> toRemove) Remove tunnels from the pool in one batch under the lock, then update stats outside the lock.(package private) booleanSynchronous tunnel removal for use during recovery or critical situations.private booleanreplaceNonGoodTunnel(TunnelInfo info, TunnelId gatewayId, long now, int usable, int maxUsable, int target) At capacity, swap the new tunnel in for the first replaceable one — only FAILED tunnels (or FAILING for non-server pools).private booleanreplaceZeroHopCandidate(TunnelInfo zeroHopTunnel, TunnelInfo tunnel) Whether the tunnel replaces the current zero-hop candidate: it expires later.(package private) voidreportSendFailure(TunnelInfo cfg, int status) Report a data-phase dispatch failure for a tunnel in this pool.private voidreportZombies(String stat, int kept, List<TunnelInfo> zombies) Record how many tunnels the health counts excluded for want of a local transport session, alongside the count that survived the filter.private voidrepublishAfterSynchronousRemoval(LeaseSet ls, int remaining) Republish the rebuilt LeaseSet, or fall back to a fresh build when no LeaseSet could be built.voidRequest new inbound tunnels so the pool holds the target count with at leastgetLeaseViabilityWindow(RouterContext)remaining.private voidRequest lease set from client for the primary and all aliases.private voidrequestLeaseSet(LeaseSet ls, boolean force) Request LeaseSet with optional force bypass of throttle.voidrequestLeaseSurge(int count) Note how many advertiseable tunnels the imminent mint needs, sopruneExcessTunnels()can offer up the ones that are no longer advertiseable until the pool has enough.private voidReset consecutive timeout counter when tunnels are working properly.private BooleanresolveRefreshForce(long now, boolean force) Resolve whether this refresh must bypass the throttle, or defer it.private voidrotateOldestLeaseForPropagation(TreeSet<Lease> leases, List<TunnelInfo> tunnels, List<TunnelInfo> rotatedOut) Rotate out the oldest lease when its end time matches the previous publication's earliest end time, so floodfill peers see wasNew=true and propagate the LeaseSet onward.(package private) static longrotateWindowStart(long windowStart, long now, long windowMs) Roll the build-rate window forward when it has expired.private TunnelPool.ScanResultscanPoolForTunnel(int startIdx, long now, boolean longTunnelsOnly, TunnelInfo lastResortTunnel) Scan the pool from a random start index for a usable tunnel.private voidDefer LeaseSet republish so rapid consecutive failures in server pools are batched into a single LeaseSet update.private voidSchedule a deferred LeaseSet refresh to fire after the throttle window expires.private voidscheduleEarlyExpiry(PooledTunnelCreatorConfig cfg, long now, long staggerMs, boolean overBudget) Schedule an early expiry for a config; the caller records it in its removal list.private voidOffer one last-chance test for each stale-but-testable UNTESTED tunnel the sweep kept.private voidschedulePriorityPeerTests(List<Hash> peersToTest) Schedule the collected peers for priority testing if the peer test job is available.private voidSeed an UNTESTED paired tunnel as GOOD on build success so the pool has at least one usable tunnel.private TunnelInfoselectFromPool(long now, long uptime, boolean avoidZeroHop) One selection pass over the pool: normal scan (long tunnels only when avoiding zero-hop), then backlogged/last-resort, then degraded.(package private) TunnelInfoPull a random tunnel out of the pool.private TunnelInfoselectTunnel(boolean allowRecurseOnFail) Select a tunnel for use, falling back to last-resort and degraded tunnels, then to a fresh zero-hop build, then to a retry.(package private) TunnelInfoselectTunnel(Hash closestTo) Return the tunnel from the pool that is XOR-closest to the target.(package private) TunnelInfoSelect a tunnel to serve as a test round's partner, preferring partners that have demonstrated they work.private voidShorten the length when under extreme stress, else clear the override.(package private) voidsetPairedPool(TunnelPool pool) Paired pool (inbound <-> outbound for same destination).(package private) voidsetSettings(TunnelPoolSettings settings) Update the settings for this pool.private booleanDetermine if we should perform detailed failure analysis Only analyze every Nth failure to avoid excessive processing.private booleanshouldBlamePaired(boolean success) Whether the paired tunnel should be blamed for this build result.(package private) static booleanshouldBypassEnsureThrottle(boolean soft, int failures, boolean overBar) Whether a data-phase failure signal is strong enough to run the ensure body immediately instead of through the throttle gate.private booleanshouldDedupeFreshBuild(long now) Dedup: skip if a deficit build ran within the last 5 seconds.(package private) static booleanshouldDeferMissingPublished(long lastRefreshTime, long now, long throttleMs) Pure defer decision when NetDB has no local LeaseSet: true when a publish is still within the throttle window (async store pending), so forcing would only re-request the same data.(package private) static booleanshouldDeferPublishedRefresh(long earliestLeaseDate, long now, long throttleMs) Pure earliest-keyed defer decision: the published LeaseSet still has full capacity (every remaining lease outlasts the throttle window), so this refresh may be deferred without losing reachability.private booleanshouldDeferRefresh(long now) When throttled: defer the refresh if the currently published LeaseSet is healthy and outlasts the defer window, or if NetDB has no local copy yet because the last publish is still in flight.(package private) static booleanshouldDropForLostSession(long firstMissingAt, long now, long hysteresis) Whether a tunnel whose local session is gone should be dropped from the pool.private booleanSuppress last-resort tunnel warning spam with rate limiting.private booleanSuppress "no tunnels available" warning spam with rate limiting Uses adaptive suppression between 5 and 10 minutes based on failures.(package private) static booleanshouldLogSendFailure(long now, long lastLogTime) Whether a data-phase failure or condemnation line should be logged now.private booleanshouldLogShortfall(long now) Whether this pool may report a materially short LeaseSet again.private booleanshouldRotateLease(long earliest) Whether the oldest lease should be rotated out: its end time matches the previous publication's earliest end time (a stable tunnel set produces the same earliest end time every build, which would otherwise never re-flood the LeaseSet).(package private) static booleanshouldSkipAsyncPublish(boolean hasPublishedLS, long lastRefreshTime, long now, long throttleMs) Pure skip decision for publishLeaseSetIfNeeded: NetDB still null but we published within the throttle window — treat as in-flight rather than missing, so tunnel-add does not re-request every build.private booleanshouldSkipDueToBackoff(int needed, boolean collapsed) Skip proactive builds while the pool's build executor is in backoff, unless the pool is truly collapsed and recovery must proceed.private booleanshouldSkipDueToDedup(int inProgress, int safeActive, long now) Dedup guard: skip the cycle when concurrent events (prune, expire, removal) all trigger within 5s.private booleanshouldSkipDueToInProgress(int safeActive, int target, int inProgress) Cap concurrent builds to prevent build storms: partial pools may run up to 2x target (capped at 6) so timed-out constructions are replaced without waiting for the slot.private booleanshouldSkipDueToInProgress(int safeActive, int target, int inProgress, double timeoutRate) Variant taking the build timeout rate, which the hot path already has.private booleanIB/OB balance: don't emergency-build if this direction already has MORE usable tunnels than its paired direction.(package private) static booleanshouldWarnSelectPeersFailure(long uptime, long now, long lastWarnTime) The selectPeers-failure WARN is rate-limited: never beforeSELECT_PEERS_FAILURE_SILENCE_MSof uptime, then at most once perSELECT_PEERS_FAILURE_WARN_INTERVAL_MSper pool.(package private) voidshutdown()Shut down the pool and clean up resources.intsize()Count every tunnel in the pool, including failed and expired ones awaiting cleanup.private long[]Snapshot of pool build state taken under a consistent lock order (_tunnelsLock, then the _inProgress monitor).static intThe soft failure count that marks a tunnel for retention.(package private) static longstampLostSession(long currentMissingAt, long now) The value to record for a tunnel whose local transport session has just been observed missing.private voidstampLostSession(TunnelInfo t, long now) Record that this tunnel's local transport session was seen missing, keeping the first observation as the start of the duration.(package private) voidstartup()Warning, this may be called more than once (without an intervening shutdown()) if the tunnel is stopped and then restarted by the client manager with the same Destination (i.e.private voidswapInReplacement(TunnelInfo replacee, TunnelInfo info, TunnelId gatewayId, long now) Swap the replacement into the pool and record it as recently added.private TunnelPool.TunnelStatssweepExpiredAndCountTunnels(long now, long preBuildThreshold) Drops expired tunnels and counts the rest.private voidBlame all peers in tunnel, with a probability inversely related to tunnel length Enhanced with intelligent blame distribution and recovery consideration.(package private) static doubletimeoutRate(long attempts, long timeouts) Fraction of counted build attempts that timed out.toString()Description of the pool and its settings.private booleantryLengthOverride(int len, int minLen, int th1, int th2, RateStat e, RateStat r, RateStat s) Whether to shorten the length under extreme stress: enough build tries in the last ten minutes, or the router just installed.(package private) voidtunnelFailed(TunnelInfo cfg) Remove tunnel and blame all peers (not necessarily equally).(package private) voidtunnelFailed(TunnelInfo cfg, Hash blamePeer) Remove the tunnel and blame only one peer.(package private) voidMark a tunnel failed without blaming any peer.private StringIdentify a tunnel by its owning destination's base32 prefix.(package private) voidupdateAdaptiveTestDuration(long durationMs) Record a completed test duration and adapt this pool's test window.(package private) static doubleupdatedAdaptiveTestMultiplier(double mult, long avgMs) Next test timeout multiplier for a given rolling average.(package private) static longupdatedAvgTestDuration(long avg, long sample) Fold a completed test duration into the rolling average.private voidupdatePairedProfile(PooledTunnelCreatorConfig cfg, boolean success) Update the paired tunnel profiles by treating the build as a tunnel test.private voidRecomputes the pool's bandwidth and lifetime rates.(package private) static intusableLeaseMinimum(int wanted) The smallest LeaseSet treated as usable for a given target.private TunnelInfouseLastResortOrBacklogged(TunnelInfo lastResort, TunnelInfo backlogged, long uptime) Prefer the recorded last-resort tunnel, then the backlogged fallback, when a scan pass found nothing better.private booleanwouldDropBelowTarget(int currentSize, int removeCount) Collapse guard: skip the prune when it would drop the pool below its effective target (base + emergency boost).(package private) static StringzombieTransportBucket(String style) Normalise a RouterInfo transport style into one of the known transports.
Field Details
EXPIRATION_COMPARATOR
Sorts tunnel infos by expiration, soonest first.QUALITY_COMPARATOR
Best tunnels first for LeaseSet publication: freshest (longest remaining life) so the lease survives floodfill propagation, then lowest average latency, then fewest consecutive failures._inProgress
Configs with builds in flight, guarded by the manager._context
The router context._log
The log._settings
The pool's current settings._tunnels
The pool's live tunnels._tunnelsLock
Lock guarding the tunnel list._peerSelector
Selector choosing peers for this pool's tunnels._manager
The manager that owns this pool._pairedPool
The opposite-direction pool for the same destination._alive
protected volatile boolean _aliveWhether this pool is running._lifetimeProcessed
private long _lifetimeProcessedTotal lifetime of tunnels this pool has used._started
private long _startedWhen the pool was started._lastRateUpdate
private long _lastRateUpdateWhen the pool's rates were last recomputed._lastLifetimeProcessed
private long _lastLifetimeProcessedLifetime total at the last rate update._rateName
Stat name prefix for this pool's rates._firstInstalled
private final long _firstInstalledWhen the pool was first installed._consecutiveBuildTimeouts
Build timeouts in a row, for stress detection.BUILD_RATE_WINDOW_MS
private static final long BUILD_RATE_WINDOW_MSFixed (tumbling) window of build outcomes for the per-pool timeout rate that scales the pre-build window — the counters reset to zero whenever the window rotates, seetimeoutRate(long, long)andcomputePreBuildWindowMs(double).- See Also:
SHORTFALL_LOG_INTERVAL_MS
private static final long SHORTFALL_LOG_INTERVAL_MSMinimum gap between repeated "not enough leases" reports from one pool.- See Also:
ZOMBIE_STAT_HEALTHY
Rate stat recording how many tunnelsgetHealthyTunnelCount()dropped for want of a transport session to their local hop.- See Also:
ZOMBIE_STAT_USABLE
Rate stat recording how many tunnelsgetUsableTunnelCount()dropped for the same reason. Kept separate fromZOMBIE_STAT_HEALTHYbecause the two counts apply different filters, so their excluded figures are not derivable from one another.- See Also:
ZOMBIE_LOG_INTERVAL_MS
private static final long ZOMBIE_LOG_INTERVAL_MSMinimum gap between repeated "health excludes N zombie tunnels" warnings from one pool. These counts run on the ensure loop rather than once a second, so without a throttle a persistently dead local hop would emit a warning per pass. Zombies are a steady-state condition rather than an event, which is also why this warns once a minute at most instead of on every change.- See Also:
_nextZombieLog
private long _nextZombieLogWall-clock guard for the zombie warning; seeZOMBIE_LOG_INTERVAL_MS._windowAttempts
Build attempts in the current stats window._windowTimeouts
Build timeouts in the current stats window._windowStartMs
private volatile long _windowStartMsWhen the current stats window began._rateWindowLock
Guards the window rotation; one lock per build completion.PRE_BUILD_WINDOW_MS
static final long PRE_BUILD_WINDOW_MSBase pre-build window: start replacements this long before existing tunnels expire so fresh builds (10-40s) complete before the old tunnels die.- See Also:
MAX_PRE_BUILD_WINDOW_MS
static final long MAX_PRE_BUILD_WINDOW_MSCap on the failure-scaled pre-build window — beyond this, starting earlier would keep nearly the whole pool's lifetime in build mode.- See Also:
LAST_CHANCE_MIN_LIFE_MS
static final long LAST_CHANCE_MIN_LIFE_MSMinimum remaining life for a stale UNTESTED tunnel to be worth one last-chance test before pruning: a test needs a minute of runway for the request/reply round trip (plus the test itself) before the tunnel expires anyway. Below this the tunnel is pruned as before.- See Also:
MAX_LAST_CHANCE_PER_SWEEP
static final int MAX_LAST_CHANCE_PER_SWEEPCap on last-chance tests enqueued per sweep pass — the global test caps still apply, but an explicit small bound keeps a fully-UNTESTED pool from flooding the first-test buffer in one ensure cycle.- See Also:
_expireStatSlot
Cached rate-stat handles so per-build lookups skip the StatManager map._rejectStatSlot
Holds the rejected-build stat once registered._successStatSlot
Holds the successful-build stat once registered._buildSuccessRateStatSlot
Holds the build success rate stat once registered._avgBWStatSlot
Holds the average bandwidth stat once registered._lastNoTunnelsWarningTime
private long _lastNoTunnelsWarningTimeWhen the last no-tunnels warning was logged._lastLastResortLogTime
private long _lastLastResortLogTimeWhen the last last-resort tunnel log was written._lastSelectPeersFailureWarnTime
private long _lastSelectPeersFailureWarnTimeWhen the last selectPeers failure warning was logged._lastSendFailureLogTime
private long _lastSendFailureLogTimeRate-limit stamps for the data-phase failure / condemnation log lines._lastCondemnWarnTime
private long _lastCondemnWarnTimeWhen the last condemn warning was logged._consecutiveEmergencies
private volatile int _consecutiveEmergenciesDynamic pool scaling: when a pool repeatedly collapses (EMERGENCY fires), increase the effective tunnel count. More parallel tunnels = more resilience against individual tunnel failures. LeaseSet publication picks the best tunnels, so excess is fine.MAX_EMERGENCY_BOOST
private static final int MAX_EMERGENCY_BOOSTMost tunnels an emergency pass may add.- See Also:
MAX_BUILD_PER_POOL_DIR
static final int MAX_BUILD_PER_POOL_DIRHard cap on concurrent builds per pool direction in normal operation. Prevents the buildComplete → ensureSufficientTunnels feedback loop from flooding the BuildExecutor when every attempt fails. Matches BuildExecutor.MAX_PER_POOL_DIR. Collapsed pools may raise the cap via shouldSkipDueToInProgress / buildReplacementTunnels. Raised from 2 so an incomplete LeaseSet / depleted pool can stage replacements in parallel instead of serializing one build per throttle. Raised from 4 to 8 because four failure-handling interventions did not move the outbound test failure rate — the bottleneck is build throughput, not failure handling. 8 parallel builds per pool direction is safe because exploratory pools already run higher concurrency without collapse.- See Also:
SOFT_REMOVAL_THRESHOLD
static final int SOFT_REMOVAL_THRESHOLDHigher removal bar for soft best-effort send timeouts (status 3). Congestion / slow destinations must not mass-remove healthy tunnels at the hard-failure bar (4), but a tunnel that times out dozens of times with no successful tests still rotates out.Must stay strictly above
SOFT_DEGRADED_FOR_ENSURE. The gap between the two is the pre-emptive build window: at the degraded count the ensure gate starts seeing a deficit and builds replacements while the tunnel is still selectable, so a pool never reaches zero usable tunnels. Equalising them collapses that window to nothing — the ensure gate only reacts once the tunnel has already been removed, which is total collapse rather than pre-emptive recovery, and is what drove 38 EMERGENCY rebuilds and 128 all-backlogged events in one short window. Lowering this to 5 (from 7) was a regression.- See Also:
PRE_EMERGENCY_FRACTION
static final double PRE_EMERGENCY_FRACTIONPre-emergency threshold: fraction of target below which builds start immediately.Half, not a quarter: at 0.25 a 4-tunnel pool only burst at zero healthy, so the burst's exemption from the global build budget engaged only after the pool had already collapsed, and recovery started from a hole while its non-exempt replacements queued behind other pools' builds. Half engages the burst and its exemption one healthy tunnel before the drain reaches zero. The threshold is exactly the
isMateriallyShort(int, int)line, which made the separate thin-pool fast-path below the pre-emergency branch unreachable; it was removed and its warning folded into the branch.- See Also:
PRE_EMERGENCY_BURST
static final int PRE_EMERGENCY_BURSTBuilds started in one pre-emergency burst. Named so it stays consistent withPRE_EMERGENCY_FRACTIONrather than sitting as a bare literal in the trigger.- See Also:
MAX_INPROGRESS_PER_POOL_DIR
static final int MAX_INPROGRESS_PER_POOL_DIRAbsolute ceiling on builds in flight per pool and direction, applied to every build path including the pre-emergency burst.Bounds the damage from any single path bypassing the normal guards. Without it a pool that stays below the emergency threshold starts a burst on every cycle while its builds neither complete nor fail, and the in-progress count climbs without limit. Observed at 99 concurrent outbound builds in a 4-tunnel pool, which starves every other pool for build capacity while fixing nothing.
- See Also:
_adaptiveAvgTestDuration
private volatile long _adaptiveAvgTestDurationRolling average test duration for this pool, ms; 0 until the first completion is recorded. Held here rather than onTestJobbecause a TestJob is created per build-request batch and discarded, so per-job state never accumulates far enough to adapt anything._adaptiveTestMultiplier
private volatile double _adaptiveTestMultiplierTest timeout multiplier for this pool, 1.0 until adapted.SLOW_TEST_AVG_MS
static final long SLOW_TEST_AVG_MSRolling average test duration above which the test window is widened.- See Also:
FAST_TEST_AVG_MS
static final long FAST_TEST_AVG_MSRolling average test duration below which the test window is narrowed.- See Also:
MAX_ADAPTIVE_TEST_MULTIPLIER
static final double MAX_ADAPTIVE_TEST_MULTIPLIERHard ceiling on the adaptive test timeout multiplier. A dead tunnel must still be condemned, so the window cannot be widened without limit.- See Also:
SOFT_DEGRADED_FOR_ENSURE
static final int SOFT_DEGRADED_FOR_ENSURESoft-failure count at which a still-usable tunnel is treated as degraded for ensure-throttle and deficit purposes. Below the soft removal bar so replacements start building before the tunnel is rotated out. A pool of soft-degraded tunnels can pass tests yet fail every data-phase send (status 3), leaving clients with half-open connects while the ensure gate still sees a "full" pool. Raised from 3 so transient congestion does not mark half the pool degraded and trip collapse-sized rebuilds every few seconds.- See Also:
SERVICE_POOL_PRIORITY
static final double SERVICE_POOL_PRIORITYPriority weight for service pool builds. Service pools carry user-facing traffic (eepsites, I2PSnark) and should get priority when the build executor is saturated.- See Also:
_lastFallbackWarnTime
private volatile long _lastFallbackWarnTimeLast time buildFallback() logged its zero-hop-refusal warning, to rate-limit it.STRUGGLE_RESERVE
private static final int STRUGGLE_RESERVEExtra tunnels maintained while the pool is struggling, so LeaseSet publication always has fresh candidates instead of re-signing the same aging leases.- See Also:
SPARE_LOW_THRESHOLD
static final int SPARE_LOW_THRESHOLDConsecutive build timeouts at which the pool starts holding one spare tunnel above target (seecomputeDifficultySpare(int)).- See Also:
SPARE_HIGH_THRESHOLD
static final int SPARE_HIGH_THRESHOLDConsecutive build timeouts at which the spare grows to two tunnels.- See Also:
IN_USE_TRAFFIC_MS
private static final long IN_USE_TRAFFIC_MSA tunnel counts as in use when it has carried verified traffic within this window. Generous on purpose: streaming retransmit chains can stall for tens of seconds (observed RTOs of 12-15s and one ACK after 151s), and tearing down the tunnel under a live stream is what turns a slow network into a dead download.- See Also:
_lastPreBuildTime
private long _lastPreBuildTimeMinimum interval between pre-build triggers per pool.pruneExcessTunnels()fires this on every ~15s cycle for every IB pool whose OB pair is depleted. Without throttling, this generates ~32 unnecessary BuildExecutor wakeups per minute, each queuing builds into an already-congested transport pipeline. The build loop's calculatePairedBuilds() already handles OB pool depletion, so the pre-build trigger is redundant — throttle it to once per 60s._everHadHealthyTunnel
private volatile boolean _everHadHealthyTunnelTrue once this pool has ever reported a non-zero healthy count. Used to distinguish a pool's initial fill (0 healthy, entirely normal) from a pool that had tunnels and drained to zero (genuine degradation worth a WARN). Set inensureSufficientTunnels()._lastThinPoolWarned
private volatile int _lastThinPoolWarnedLast healthy count emitted in a thin-pool warning, or -1 before any.The executor ticks far faster than the pool's health changes, so warning per tick reprinted an unchanged 2/8 hundreds of times a minute. That is worse than noisy: it buries the build failures it sits next to, and a repeated identical value carries no more information than one line.
PRE_BUILD_THROTTLE_MS
private static final long PRE_BUILD_THROTTLE_MSPre-build passes are at least 1 minute apart.- See Also:
_lastEmergencyBuildTime
private volatile long _lastEmergencyBuildTimeWhen the last emergency build pass ran.EMERGENCY_COOLDOWN_MS
private static final long EMERGENCY_COOLDOWN_MSAn emergency build pass is followed by a 30 second wait.- See Also:
EMERGENCY_COLLAPSE_COOLDOWN_MS
private static final long EMERGENCY_COLLAPSE_COOLDOWN_MSShorter cooldown during total collapse — when a pool has zero usable AND zero untested tunnels, a 30s cooldown leaves the pool stranded for too long. The first EMERGENCY build takes ~20s to timeout; with a 30s cooldown the next attempt is 50s later. During this window all connection attempts to the associated destination fail.- See Also:
_leaseSetRepublishPending
private volatile boolean _leaseSetRepublishPendingSet when a lease set republish is queued._ensuringTunnels
Reentrancy guard for ensureSufficientTunnels().pruneNonGoodTunnels()calls removeTunnel() per-tunnel, and removeTunnel() calls ensureSufficientTunnels() — creating recursive build storms that inflate _inProgress and trigger 800+ "Cancelling excess" per session._lastDeficitBuildTime
private volatile long _lastDeficitBuildTimeLast time ensureSufficientTunnels() started a batch of builds. Prevents build storms when concurrent events (prune, expire, removal) all trigger ensureSufficientTunnels() within milliseconds. Builds take 10-40s. Starting duplicate batches within 5s wastes capacity and fills the pool with UNTESTED tunnels that block the addTunnel cap._leaseSurgeNeed
private volatile int _leaseSurgeNeedLease-material target override, or 0 when none is active.A pool normally replenishes toward
getEffectiveTarget(): enough tunnels to serve traffic. That is the right target most of the time, but it is blind to a second question. A pool can sit at full size while holding nothing with enough life left to be advertised --pruneExcessTunnelsonly prunes once the pool exceeds target, and it skips anything recently active, so a near-expiry tunnel is retained indefinitely and never counted as fresh bycountFreshTunnels. The result is an inbound pool that looks healthy by count and cannot sign a LeaseSet worth publishing.This field bridges that gap, by way of pruning only.
RepublishLeaseSetJobraises it when a mint is imminent and the pool cannot supply viable leases;pruneExcessTunnels()then offers up tunnels that can no longer be advertised, dropping the pool below its target and so provoking the rebuild. It is deliberately NOT a build target: the required count is at most 2 andgetActiveTarget()is at least 2, so folding it intogetEffectiveTarget()as a floor could never move the number. Build pressure comes from the shortfall, not from inflating the target.The alternative to pruning here is raising
LEASE_MIN_REMAINING_MSpermanently, which buys the same effect by making every tunnel past a fixed age ineligible -- sustained extra build load on a path that is already denied most of its demand.ENSURE_THROTTLE_MS
private static final long ENSURE_THROTTLE_MSNormal minimum interval between ensureSufficientTunnels() runs for a healthy or partially-degraded pool (usable tunnels > 1).- See Also:
ENSURE_COLLAPSED_MIN_MS
private static final long ENSURE_COLLAPSED_MIN_MSFloor between ensureSufficientTunnels() runs when the pool is collapsed (usable tunnels <= 1). The long gate is bypassed so recovery is not delayed, but this short floor still spaces prune+sweep work when buildComplete → ensure fires rapidly after fast-failing builds.- See Also:
ENSURE_DEGRADED_MS
private static final long ENSURE_DEGRADED_MSIntermediate ensure gate while the published LeaseSet is incomplete (missing required leases) but the pool is not fully collapsed. Keeps JobQueue a breather without holding replacements for the full healthy 15s window — a thin LeaseSet is a reachability outage.- See Also:
_lastRemovalTime
private volatile long _lastRemovalTimeLast time a tunnel was removed from this pool. A removal after the last ensure run is a liveness signal: the pool just shrank, so the next ensure should use the shortENSURE_COLLAPSED_MIN_MSfloor instead of waiting out the healthy 15s gate.DEFICIT_THROTTLE_MS
private static final long DEFICIT_THROTTLE_MSMinimum interval between deficit-build batches on a non-collapsed pool.- See Also:
DEFICIT_COLLAPSE_COOLDOWN_MS
private static final long DEFICIT_COLLAPSE_COOLDOWN_MSShorter deficit-build spacing while collapsed (zero healthy-safe tunnels, or <= 1 with nothing in flight). Not a full bypass: fast-failing builds would otherwise re-enter selectSingleHop on every completion and burn JobQueue CPU. An empty pool uses this cooldown even with builds in flight. Hung builds (17-30s timeouts) must not pin recovery on the 30s healthy gate. MatchesEMERGENCY_COLLAPSE_COOLDOWN_MSso recovery stays prompt without build-storming; concurrent builds stay capped byshouldSkipDueToInProgress(int, int, int).- See Also:
DEFICIT_DEGRADED_MS
private static final long DEFICIT_DEGRADED_MSIntermediate deficit-build spacing while the LeaseSet is incomplete but the pool still has usable tunnels (not collapsed). Between the collapse cooldown and the healthy 30s gate so a thin LeaseSet refills without hammering peer selection on every cycle.- See Also:
_lastEnsureTime
private volatile long _lastEnsureTimeThrottle for ensureSufficientTunnels hot path (per pool). SeeENSURE_THROTTLE_MSandENSURE_COLLAPSED_MIN_MS._preEmergencyBurstAt
private volatile long _preEmergencyBurstAtLast time this pool dispatched a pre-emergency burst, or 0 if it never has.The executor reads this to decide which in-flight builds are governed by this pool's own
MAX_INPROGRESS_PER_POOL_DIRcap rather than by the global dispatch budget. Without that separation the global budget — derived from outbound bandwidth and therefore deliberately low — subtracts the work of a pool it does not know is in distress, and starves it.PRE_EMERGENCY_EXEMPT_WINDOW_MS
private static final long PRE_EMERGENCY_EXEMPT_WINDOW_MSHow long a pre-emergency burst keeps its in-flight builds exempt from the global dispatch budget. Long enough to cover the replenishment the burst was started for and the timeouts that follow it, short enough that a pool which recovered stops being exempt.- See Also:
DEFAULT_PRUNE_EARLY_EXPIRY
static final long DEFAULT_PRUNE_EARLY_EXPIRYDefault early expiration time for pruned tunnels (120 seconds).- See Also:
PROP_PRUNE_EARLY_EXPIRY
Config key for the early tunnel expiry margin.- See Also:
LEGACY_PROP_PRUNE_EARLY_EXPIRY
Legacy alias forPROP_PRUNE_EARLY_EXPIRY, read only as a fallback.- See Also:
PRUNE_KEEP_IF_FRESH_MS
private static final long PRUNE_KEEP_IF_FRESH_MSNon-published tunnels with more remaining life than this are fresh — never pruned.- See Also:
PRUNE_NEAR_EXPIRY_MS
private static final long PRUNE_NEAR_EXPIRY_MSNon-published tunnels with less remaining life than this are pruned at publication.- See Also:
BUILD_TRIES_LENGTH_OVERRIDE_1
private static final int BUILD_TRIES_LENGTH_OVERRIDE_1If less than one success in this many, reduce length (exploratory only).- See Also:
BUILD_TRIES_LENGTH_OVERRIDE_2
private static final int BUILD_TRIES_LENGTH_OVERRIDE_2Build tries override for the second consecutive failure.- See Also:
BUILD_TRIES_LENGTH_OVERRIDE_CLIENT_1
private static final int BUILD_TRIES_LENGTH_OVERRIDE_CLIENT_1Build tries override for a client pool's first failure.- See Also:
BUILD_TRIES_LENGTH_OVERRIDE_CLIENT_2
private static final int BUILD_TRIES_LENGTH_OVERRIDE_CLIENT_2Build tries override for a client pool's second failure.- See Also:
LEASE_SAFETY_MARGIN
private static final long LEASE_SAFETY_MARGINLease end is set this long before the tunnel expires, so peers re-fetch the new LeaseSet while the gateway still routes instead of racing tunnel death.- See Also:
MAX_TUNNEL_LIFETIME_MS
static final long MAX_TUNNEL_LIFETIME_MSHard ceiling ongetTunnelLifetime(RouterContext): the standard 10m lifetime plus the 1m grace period other routers afford. The published lease is capped separately atgetLeaseMaxDuration(RouterContext)(10m), so the tunnel runs one grace period past the lease it advertises — which is what keeps the advertised value on the standard 10m. Exceeding 11m buys no overlap: the lease cap binds first, so a longer tunnel is simply paid for during a window no LeaseSet covers.- See Also:
LEASE_MIN_REMAINING_MS
private static final long LEASE_MIN_REMAINING_MSTunnels must have at least this much remaining life to be published.The lease ends
LEASE_SAFETY_MARGINbefore the tunnel expires and the lease end is floored 60s out, so a tunnel closer to death than this would produce a lease ending after the gateway stops routing.Raised from two minutes to three. This is the admission gate for LeaseSet leases, and it is what pins published LeaseSets near the floor: a LeaseSet's staleness is judged on its latest lease (see
LeaseSet.isCurrent()), so admitting near-dead tunnels let one two-minute lease cap the advertised lifetime of the whole set. Client destinations were publishing LeaseSets at 76-120s rather than the 600s theleaseMaxDurationcap allows.Three minutes is the compromise that still admits tunnels: a tunnel lives 11 minutes, so it is eligible for roughly the first 8. Ten minutes would only be satisfiable in a tunnel's first minute and would starve the pools.
getLeaseViabilityWindow(RouterContext)is the matching window the re-mint side already uses.Effect on pool size: the floor is also an eligibility gate, so a pool short on fresh tunnels now publishes a smaller LeaseSet rather than one full of short-lived leases.
- See Also:
FAILSAFE_CLOSE_WINDOW_MS
private static final long FAILSAFE_CLOSE_WINDOW_MSHow recently a transport must have closed an idle session for the loss to be attributed to this router. Matches the memory the transport keeps.- See Also:
LOST_SESSION_DROP_HYSTERESIS_MS
private static final long LOST_SESSION_DROP_HYSTERESIS_MSHow long a local transport session must stay missing before the tunnel is dropped rather than merely excluded from the health counts.The session can come back on its own, and rebuilding a tunnel that its peer was about to re-establish anyway is pure churn. Waiting this long costs a slot that is not carrying traffic either way, and is recovered the moment the build lands.
- See Also:
_lastSessionLossProbe
private volatile long _lastSessionLossProbeLast time a lost-session cause was sampled. The probe reaches the transport and must not run per exclusion, so one sample per interval is all the split needs to accumulate.SESSION_LOSS_PROBE_INTERVAL_MS
private static final long SESSION_LOSS_PROBE_INTERVAL_MSInterval between lost-session attribution samples.- See Also:
RECENTLY_ADDED_WINDOW
private static final long RECENTLY_ADDED_WINDOWTrack recently-added tunnel IDs to prevent duplicates. Uses a simple sliding window based on add time. Window is 60 seconds — long enough to catch duplicateaddTunnel(TunnelInfo)calls for the same tunnel.- See Also:
_lastRefreshTime
private long _lastRefreshTimeThrottle refresh — publish at most once per throttle window. 2 min minimum (refresh throttle default) prevents storms; with occasional emergency publishes the actual interval averages ~10 min, driven by the lease duration cap. Initialize to allow first request immediately._lastLeaseSetPublishTime
private long _lastLeaseSetPublishTimeTrack last proactive LeaseSet publish time for rate limiting._cachedLeaseSet
Minimum interval between LeaseSet builds (matches getRefreshThrottle(_context)). Prevents rapid LeaseSet object churn on every tunnel add/remove. Cached LeaseSet returned during rate-limit window._lastLeaseSetBuildTime
private long _lastLeaseSetBuildTimeTimestamp of the last successful LeaseSet build._hasGoodLeaseSet
private boolean _hasGoodLeaseSetTrue after first LeaseSet with at least one lease was built (not necessarily GOOD)._hasIncompleteLeaseSet
private boolean _hasIncompleteLeaseSetTrue when last built LeaseSet had fewer leases than wanted — bypass cache._lastShortfallLog
private volatile long _lastShortfallLogWhen this pool last reported a materially short LeaseSet_lastPublishedEarliestLeaseEnd
private long _lastPublishedEarliestLeaseEndEarliest lease end time of the last published LeaseSet. Used to detect same-lease-stall: if the new LS has the same earliest end time, the oldest lease is rotated out so floodfill peers see wasNew=true._pendingRefreshScheduled
Track if a deferred refresh is already scheduled._recentlyAddedTunnels
_nearExpiryCount
private volatile int _nearExpiryCountLive tunnels whose lease is inside the pre-build window, published by the last sweep soBuildExecutor's dispatch ordering can read it without taking the pool lock.Published rather than computed on demand because the executor scores every wanting pool on every dispatch cycle. Walking the pool's tunnels per score would be O(tunnels x pools) per cycle, behind a lock the pool needs for its own work. A count one sweep stale is the right trade: it is refreshed on every sweep, and a pool that has just expired a tunnel is already being told to build by the deficit term.
SEND_FAILURE_LOG_INTERVAL_MS
private static final long SEND_FAILURE_LOG_INTERVAL_MSMinimum gap between data-phase failure log lines, per pool.- See Also:
SELECT_PEERS_FAILURE_SILENCE_MS
private static final long SELECT_PEERS_FAILURE_SILENCE_MSSilent in the first 3 minutes of uptime: peers are still being discovered.- See Also:
SELECT_PEERS_FAILURE_WARN_INTERVAL_MS
private static final long SELECT_PEERS_FAILURE_WARN_INTERVAL_MSAt most one selectPeers-failure WARN per interval per pool.- See Also:
Constructor Details
TunnelPool
TunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel) Tunnel pool.- Parameters:
ctx- the router contextmgr- the pool manager owning this poolsettings- the pool's tunnel quantity, direction and exploratory flagssel- the peer selector used to choose tunnel build participants
Method Details
compareAvgLatency
Latency ascending, tunnels with no measurements sort last.- Parameters:
a- the tunnel to order firstb- the tunnel to order against it- Returns:
- negative when a is faster, positive when b is faster, 0 when they are equally fast or neither has a measurement
updatedAvgTestDuration
static long updatedAvgTestDuration(long avg, long sample) Fold a completed test duration into the rolling average.Pure so the smoothing is testable without a router.
- Parameters:
avg- current average, 0 if nonesample- the duration just observed, ms- Returns:
- the updated average
updatedAdaptiveTestMultiplier
static double updatedAdaptiveTestMultiplier(double mult, long avgMs) Next test timeout multiplier for a given rolling average.Pure. Rises when tests are consistently slow so a slow-but-alive tunnel is not condemned for exceeding a window that was never long enough for it, and falls back when they are not. Capped at
MAX_ADAPTIVE_TEST_MULTIPLIERso a persistently slow path cannot widen the window without bound and mask genuinely dead tunnels.- Parameters:
mult- current multiplier, 1.0 up toMAX_ADAPTIVE_TEST_MULTIPLIERavgMs- rolling average test duration, ms- Returns:
- the updated multiplier
updateAdaptiveTestDuration
void updateAdaptiveTestDuration(long durationMs) Record a completed test duration and adapt this pool's test window.- Parameters:
durationMs- elapsed test time, ms
getAdaptiveAvgTestDuration
long getAdaptiveAvgTestDuration()This pool's rolling average of completed test durations, used to adapt the test timeout multiplier.- Returns:
- this pool's rolling average test duration, 0 if none recorded
getAdaptiveTestTimeout
long getAdaptiveTestTimeout(long baseTimeout) Adapt a base test timeout to this pool's observed test speed.- Parameters:
baseTimeout- the configured timeout, ms- Returns:
- the adapted timeout, ms
isPreEmergencyBurstLive
static boolean isPreEmergencyBurstLive(long burstAt, long now, long window) Whether this pool dispatched a pre-emergency burst recently enough that its in-flight builds are governed by the per-pool cap instead of the global budget. Pure decision helper for unit tests.- Parameters:
burstAt- last pre-emergency burst time in ms, or 0 if nevernow- current time in mswindow- how long a burst keeps its builds exempt- Returns:
- true when in-flight builds of this pool bypass the global reduction
isPreEmergencyExempt
boolean isPreEmergencyExempt(long now) Package-visible for the build executor: whether this pool's in-flight builds should bypass the global dispatch budget.- Parameters:
now- current time in ms- Returns:
- true when a pre-emergency burst is still recent
getPruneEarlyExpiry
long getPruneEarlyExpiry()Early expiry time for pruned tunnels. Reads fromrouter.pruneEarlyExpiryDelay, falling back to the legacyrouter.tunnel.pruneEarlyExpiryDelayalias, then the default (120s).- Returns:
- early expiry time in milliseconds
getPruneEarlyExpiry
Early expiry time for pruned tunnels, shared by every prune path (pool sweep and Remove Slow) so one pool's tunnels cannot be given a different head start than another's for the same configuration. Precedence: canonicalPROP_PRUNE_EARLY_EXPIRYfirst, then the legacyLEGACY_PROP_PRUNE_EARLY_EXPIRYalias kept for configs and scripts that predate the canonical name, elseDEFAULT_PRUNE_EARLY_EXPIRY.- Parameters:
ctx- the router context- Returns:
- early expiry time in milliseconds
getTunnelLifetime
Tunnel lifetime from config or default (11 minutes).The tunnel outlives its own published lease by
LEASE_SAFETY_MARGIN(60s). That difference is the grace period, and it is what makes the advertised value land on the I2P standard: the lease end ismin(tunnel expiry, now + leaseMaxDuration) - 60s, so with an 11m tunnel and the 10mgetLeaseMaxDuration(RouterContext)cap the cap binds and peers see ~10m. Shortening the tunnel below the cap would drag the advertised lease down with it — a 10m tunnel advertises only 9m — so the tunnel is deliberately held one grace period above the lease cap.Hard-capped at 11m: other routers afford a 1m grace period beyond the standard 10m lifetime and no more, so a longer tunnel would be paid for during a window no LeaseSet covers and would outlive what peers will tolerate caching.
Tunable via
i2p.tunnel.lifetime(default: 660000, capped at 660000).- Parameters:
ctx- the router context- Returns:
- the tunnel lifetime in milliseconds, never above 11 minutes
getLeaseViabilityWindow
A lease counts toward a re-mint while it has at least this much remaining: three minutes covers the re-sign request, floodfill propagation, and initial connection establishment, and a tunnel closer to expiry than this is already inside the pool's proactive replacement window (BuildExecutor starts replacing GOOD tunnels 330s out), so counting it would overstate the pool's health. Shrinks toward two minutes for very short lifetimes so short-lifetime configs aren't stuck deferring forever.- Parameters:
ctx- the router context- Returns:
- the lease viability window in ms
getMaxConcurrentBuildsPerDirection
Max concurrent builds per direction from config or default (6). Tunable viai2p.tunnel.maxConcurrentBuildsPerDirection(default: 6).- Parameters:
ctx- the router context- Returns:
- the max concurrent builds per direction
getStartupTime
Startup suppression period from config or default (5 minutes). Tunable viai2p.tunnel.startupTime(default: 300000).- Parameters:
ctx- the router context- Returns:
- the startup time in milliseconds
getBuildTriesQuantityOverride
Quantity override threshold from config or default (12). If less than one success in this many, reduce quantity. Tunable viai2p.tunnel.buildTriesQuantityOverride(default: 12).- Parameters:
ctx- the router context- Returns:
- the quantity override threshold
getRefreshThrottle
Refresh throttle interval from config or default (2 minutes). Minimum interval between LeaseSet publishes, so a failed inbound tunnel is dropped from the published LeaseSet promptly while staying under the floodfill republish budget (~6 per destination per 10 min). Tunable viai2p.tunnel.refreshThrottle(default: 120000).- Parameters:
ctx- the router context- Returns:
- the refresh throttle interval in milliseconds
getLeaseSetBuildMinInterval
LeaseSet build minimum interval from config or default (2 minutes). Minimum interval between LeaseSet object rebuilds, kept at or below the refresh throttle so a republish always reflects the current tunnel set (including newly built tunnels and excluding failed ones). Tunable viai2p.tunnel.leasesetBuildMinInterval(default: 120000).- Parameters:
ctx- the router context- Returns:
- the LeaseSet build minimum interval in milliseconds
getLeaseMaxDuration
Maximum lease set lease duration from config or default (10 minutes). Lease end dates in published LeaseSets are capped to this value from now, so peers re-fetch our LeaseSet sooner than the 11-minute tunnel lifetime. The tunnel itself continues to process messages; only the cached LeaseSet on the requesting side expires earlier, triggering a re-fetch.The 5-minute republish cycle re-floods (or re-mints) well before a lease dies: the re-mint floor re-signs any stored copy under 10 minutes, and the reschedule anchors each check inside the 5-minute expiry window. Set "
i2p.tunnel.leaseMaxDuration" explicitly to override.- Parameters:
ctx- the router context- Returns:
- the maximum lease duration in milliseconds
startup
void startup()Warning, this may be called more than once (without an intervening shutdown()) if the tunnel is stopped and then restarted by the client manager with the same Destination (i.e. for servers or clients w/ persistent key, or restarting close-on-idle clients).publishInitialLeaseSet
private void publishInitialLeaseSet()Build and publish the first LeaseSet on startup for server pools so clients can find the destination immediately.computeStartupName
Display name for the rate stat: "Exploratory tunnels" for exploratory pools, otherwise the destination nickname (HTML-stripped) or the destination's base32 address.- Returns:
- the label for this pool's rate stat, never null
createRateStatForName
Register the pool's bandwidth rate stat.- Parameters:
name- display name the stat is labelled with, from computeStartupName()
shutdown
void shutdown()Shut down the pool and clean up resources.getRateName
getTunnelPoolManager
TunnelPoolManager that owns this pool.- Returns:
- non-null
getRateStat
Get a rate stat, caching the handle so per-build lookups skip the StatManager lookup. Null results are not cached — the stat may be registered after the first use.- Parameters:
slot- the cached handle, null until first foundname- the stat name- Returns:
- the stat, or null if not registered
getAvgBWPerTunnel
public int getAvgBWPerTunnel()Average bandwidth per tunnel in the pool.- Returns:
- average bandwidth per configured tunnel in Bps
refreshSettings
private void refreshSettings()Re-reads the pool's settings from the config.selectTunnel
TunnelInfo selectTunnel()Pull a random tunnel out of the pool. If there are none available but the pool is configured to allow 0hop tunnels, this builds a fake one and returns it.- Returns:
- null on failure, but it should always build and return a fallback
selectTunnel
Select a tunnel for use, falling back to last-resort and degraded tunnels, then to a fresh zero-hop build, then to a retry.- Parameters:
allowRecurseOnFail- true to retry once after building a fallback- Returns:
- null on failure, but it should always build and return a fallback
selectFromPool
One selection pass over the pool: normal scan (long tunnels only when avoiding zero-hop), then backlogged/last-resort, then degraded. Logs the no-tunnels warning (rate-limited) when the pool is empty.- Parameters:
now- current time in msuptime- router uptime in msavoidZeroHop- true to skip zero-hop tunnels on the first pass- Returns:
- a usable tunnel, or null
handleBacklogged
Handle a pass whose chosen tunnel was null but which recorded a backlogged candidate: warn and fall back to it. Only the first pass (avoidZeroHop) uses this, since the second pass already includes last-resort tunnels.- Parameters:
pass- the scan result to take a backlogged candidate from- Returns:
- the backlogged tunnel, or null
useLastResortOrBacklogged
private TunnelInfo useLastResortOrBacklogged(TunnelInfo lastResort, TunnelInfo backlogged, long uptime) Prefer the recorded last-resort tunnel, then the backlogged fallback, when a scan pass found nothing better.- Parameters:
lastResort- the tunnel kept as a fallback of last resort, or nullbacklogged- the tunnel whose local hop is backlogged, or nulluptime- router uptime in ms, used to silence the warning while the router is still warming up- Returns:
- the tunnel to use, or null if neither exists
selectTunnelForTest
TunnelInfo selectTunnelForTest()Select a tunnel to serve as a test round's partner, preferring partners that have demonstrated they work.Distinct from
selectTunnel(), which serves the live data path and must keep its uniform-random choice: changing that would alter which tunnel carries real traffic, which is well outside what a diagnosis warrants. This is used only where a test round picks the leg its reply comes back through.A test round is a composite of the tunnel under test and the partner the reply arrives through, and a single pass/fail bit cannot separate them. Choosing partners uniformly at random therefore injects a source of noise that looks exactly like a failing tunnel: a partner whose gateway has gone quiet since the tunnel was built still satisfies every status gate, because those gates inspect the tunnel and never its peers. Ranked preference costs nothing and cannot reject a working partner, because every tier falls through to the existing behaviour.
Preference order, best first:
- tested GOOD and carrying recent real traffic — the only tier that proves the tunnel works end to end, since receiving data means its gateway is delivering
- tested GOOD
- carrying real traffic, status not yet known
- anything passing the scan gates (the previous behaviour)
Deliberately a preference and not a filter. A transport-level reachability probe on the gateway was considered and rejected: the gateway is a peer we may hold no session with, or an established but idle one, so treating "not established" as disqualifying would discard good partners and could make the round trip worse.
- Returns:
- the chosen tunnel, or null if none passes the scan gates
partnerPreferenceTier
Rank a candidate test partner; lower is better. SeeselectTunnelForTest()for why this is a preference.- Parameters:
info- a tunnel that already passed the scan gates- Returns:
- the preference tier, 0 best
scanPoolForTunnel
private TunnelPool.ScanResult scanPoolForTunnel(int startIdx, long now, boolean longTunnelsOnly, TunnelInfo lastResortTunnel) Scan the pool from a random start index for a usable tunnel. Skips last-resort tunnels (recording the first seen), failed tunnels, and tunnels over the max consecutive-failure cap — test failures are often reply-path problems, not tunnel quality, so let data prove the tunnel works. A candidate is accepted when it is non-expired and its next peer is not backlogged (inbound pools skip the backlog check, and short tunnels are exempt). The first pass additionally requires length > 1. Backlogged candidates are recorded as a fallback.- Parameters:
startIdx- where the scan starts, for a positionally unbiased passnow- current time in ms, for the expiry checklongTunnelsOnly- first-pass mode (zero-hop-avoiding pools): long tunnels onlylastResortTunnel- first-pass result, kept by the second pass if set- Returns:
- the scan result
passesScanGates
Whether a pooled tunnel passes the scan eligibility gates: not failed, not over the consecutive-test-failure cap (test failures are often reply-path problems, not tunnel quality), non-expired, and — on the first pass. Longer than a single hop. Pure decision. No side effects.- Parameters:
info- the candidate tunnelnow- current time in mslongTunnelsOnly- first-pass mode (zero-hop-avoiding pools): long tunnels only- Returns:
- whether the tunnel is a scan candidate
pickDegradedTunnel
Last-resort scan: accept any non-expired tunnel, even one over the consecutive-failure cap. Retained tunnels are kept deliberately and are better than returning null (which causes "No tunnels available" and lost build replies). Applies to ALL pools, even exploratory, since a null return can disrupt exploration.- Parameters:
now- current time in ms, compared against each tunnel's expiration- Returns:
- a non-expired tunnel, or null
classifyPoolState
static TunnelPool.PoolState classifyPoolState(List<TunnelInfo> tunnels, long now, java.util.function.Predicate<TunnelInfo> backlogged) Count the pool by selection outcome. Pure: no lock, no clock, no logging.A tunnel is counted as usable when it clears the same gates
passesScanGates(TunnelInfo, long, boolean)applies and is not set aside, so a pool reportingUsable: 0against a non-zero pool count has tunnels it is refusing to use -- which is the case worth investigating.- Parameters:
tunnels- the pool's tunnels, not necessarily lockednow- current time in msbacklogged- tests whether a tunnel's next peer is backlogged- Returns:
- the classified counts
backlogTest
Backlog predicate matching the onescanPoolForTunnel(int, long, boolean, TunnelInfo)applies.- Returns:
- the predicate, or null for an inbound pool, whose tunnels have no next peer to be backlogged
logNoTunnelsWarning
Log the no-tunnels warning, distinguishing an unreachable destination (no LeaseSet found) from a pool that holds nothing usable.- Parameters:
state- pool composition captured when the failure was detected
recordPooledActivity
Record activity on a pooled tunnel (no-op for non-pooled).- Parameters:
info- the tunnel just used, only counted if it is a pooled one
shouldLogLastResortWarning
private boolean shouldLogLastResortWarning()Suppress last-resort tunnel warning spam with rate limiting.- Returns:
- whether log last resort warning
selectTunnel
Return the tunnel from the pool that is XOR-closest to the target. By using this instead of the randomselectTunnel(), we force some locality in OBEP-IBGW connections to minimize those connections network-wide.Does not check for backlogged next peer. Does not return an expired tunnel.
- Parameters:
closestTo- the hash to find the closest tunnel to- Returns:
- the tunnel closest to the target, or null on failure
findClosestTunnel
Find the non-failed tunnel XOR-closest to the target, so OBEP-IBGW connections keep some locality network-wide. Tunnels over the max consecutive-failure cap are skipped — test failures are often reply-path problems, not tunnel quality.- Parameters:
closestTo- the hash the tunnel should be XOR-closest toavoidZeroHop- true to leave single-hop tunnels out of the runningnow- current time in ms, compared against each tunnel's expiration- Returns:
- the closest tunnel, or null
pickAnyNonExpiredTunnel
Pick any live tunnel from the pool, expired ones excluded.- Parameters:
now- current time in ms, compared against each tunnel's expiration- Returns:
- any non-expired tunnel, or null
getTunnel
Tunnel by its gateway tunnel ID.- Parameters:
gatewayId- for inbound, the GW rcv tunnel ID; for outbound, the GW send tunnel ID.- Returns:
- the tunnel with the matching gateway ID, or null if not found
listTunnels
List of tunnels currently in the pool.- Returns:
- a copy of the list of TunnelInfo objects
needFallback
boolean needFallback()Do we really need more fallbacks? Used to prevent a zillion of them. Does not check config, only call if config allows zero hop.- Returns:
- true if more fallback tunnels are needed
hasZeroHopFallback
boolean hasZeroHopFallback()Whether any active tunnel in this pool is a zero-hop fallback while the pool is configured for multi-hop. Such tunnels are bootstrap placeholders: they provide no anonymity and must be replaced as fast as possible, so the executor treats their presence as an emergency that bypasses build budget caps.- Returns:
- true if a length-1 tunnel exists in this pool
countZeroHopFallbacks
int countZeroHopFallbacks()Count the zero-hop fallback tunnels in this pool.- Returns:
- number of length-1 tunnels
setLengthOverride
private void setLengthOverride()Shorten the length when under extreme stress, else clear the override. We only do this for exploratory tunnels, since we have to build a fallback if we run out. It's much better to have a shorter tunnel than a fallback.computeAndApplyLengthOverride
private boolean computeAndApplyLengthOverride(int len) Compute the ten-minute build success and shorten the override length under stress, using the thresholds and rate stats for this pool type.- Parameters:
len- the pool's configured tunnel length- Returns:
- true if an override was applied
tryLengthOverride
private boolean tryLengthOverride(int len, int minLen, int th1, int th2, RateStat e, RateStat r, RateStat s) Whether to shorten the length under extreme stress: enough build tries in the last ten minutes, or the router just installed.- Parameters:
len- the pool's configured tunnel lengthminLen- the floor for the shortened lengthth1- build tries needed in the window before the override is considered, and the 1-in-th1 success ratio that shortens the length by one hopth2- the 1-in-th2 success ratio that shortens it by two hopse- expire rate stat for this pool type, or nullr- reject rate stat for this pool type, or nulls- success rate stat for this pool type, or null- Returns:
- true if the length override was applied
applyLengthOverride
private boolean applyLengthOverride(int len, int minLen, int th1, int th2, long tot, long sc) Shorten the override length based on the ten-minute success rate.- Parameters:
len- the pool's configured tunnel lengthminLen- the floor for the shortened lengthth1- the 1-in-th1 success ratio that shortens by one hopth2- the 1-in-th2 success ratio that shortens by two hopstot- build tries in the window: expire plus reject plus successsc- successful builds out of tot- Returns:
- true if the override was applied
listPending
List of tunnelInfo instances of tunnels currently being built.- Returns:
- the list of tunnels currently being built
getUsableTunnelCount
int getUsableTunnelCount()Count tunnels that are usable for routing — not expired, not failed, not marked FAILING. Used by collapse detection and the EMERGENCY balance check so zombie tunnels don't skew the comparison. Does not exclude near-expiry tunnels; callers that care about lease viability use their own windows.- Returns:
- the number of usable tunnels
getHealthyTunnelCount
int getHealthyTunnelCount()Usable tunnels that are not soft-degraded: same filters asgetUsableTunnelCount()plus a floor onTunnelInfo.getSoftFailures(). Soft-degraded tunnels may still pass tests while failing every data-phase send, so counting them as healthy would hold the ensure gate at the long throttle while the pool cannot carry traffic.Also excludes tunnels whose next hop is backlogged. A backlogged peer is congested, not healthy: it can pass tests and still time out every data-phase send, which is exactly the condition that produced 128 consecutive "All tunnels are backlogged" events while the pool reported itself full and the ensure gate therefore never built.
- Returns:
- the number of healthy (non soft-degraded, non backlogged) usable tunnels
reportZombies
Record how many tunnels the health counts excluded for want of a local transport session, alongside the count that survived the filter.Without this the zombie exclusion is unobservable: the counts silently changed what they report, and nothing in the log or the stats graph showed how many tunnels the filter actually caught. A pool reporting 6 healthy of 8 because two are zombies is a very different situation from one reporting 6 healthy of 6, and only the excluded count tells them apart.
Both figures are passed in rather than re-read from the sibling count, because that count calls back into here: reading it here would recurse. The two counts also filter differently (healthy additionally drops soft-degraded and backlogged tunnels), so their totals are not derivable from each other.
The stat is written on every pass, including zero, so the rate graph shows the pool as healthy-or-zombie rather than simply going silent when there are no zombies. The log line is rate-limited because these counts run on the ensure loop, not once a second.
- Parameters:
stat- which health count produced this pass, one ofZOMBIE_STAT_HEALTHYorZOMBIE_STAT_USABLEkept- the number of tunnels this count is returning to its callerzombies- the tunnels dropped for want of a local transport session, or null when there were none
localHop
The peer whose transport session this tunnel sends through, or null when there is none. Direction-dependent, mirroringBuildRequestor.getBuildRequestPeer.- Parameters:
t- the pool tunnel; may be null or zero-length- Returns:
- the local hop, or null
countSessionLossCause
Attribute one zombie exclusion to this router or to the peer, and count it.The transport records the idle closes it performs, so the question can be answered from a fact rather than inferred reachability: a peer we closed an idle session to is one this router declined to keep talking to. Everything else - a peer that went away, a path that broke, a session closed for another reason - is not attributable and stays UNKNOWN, which is the honest answer when the evidence does not support one.
- Parameters:
hop- the zombie's local hop; may be null
isLocalHopUnreachable
Whether this tunnel's local path is unusable because we hold no transport session to the peer we actually send through.A tunnel is a crypto path above the transport, and it resolves its hop per message. So when a transport session dies the
TunnelInfoand its lease survive untouched: nothing marks the tunnel failed, andgetHealthyTunnelCount()-- which tests only expiration, test status, soft failures and next-hop backlog -- keeps counting it. The result is a zombie that occupies a pool slot and inflates every healthy/usable figure the pool and its throttles are driven by, while carrying nothing.The peer index is direction-dependent, which is the trap here. Mirroring
BuildRequestor.getBuildRequestPeer: for an inbound tunnelgetPeer(0)is the peer we send to, but for outboundgetPeer(0)is the gateway we want to reach andgetPeer(1)is our next hop. CheckinggetPeer(0)unconditionally would query a session we never had for every outbound tunnel and mark them all dead.Only the local hop can be checked. Hops beyond it are inside the network and are reached through the tunnel, so we hold no session to them; a failure there is indistinguishable from a far-end failure. That limit is why this is a complement to the tester rather than a replacement for it.
A tunnel is only excluded when its local session has been lost since it was built, never merely absent.
TunnelInfo.hadLocalHopSession()snapshots the state ataddTunnel(TunnelInfo), when the session must have existed for the build to complete. ReadingisEstablishedon its own cannot tell a genuine zombie from a session that is still coming up, and with the observed exclusion rate near 20% of pool slots that distinction is the difference between measuring real loss and measuring our own reconnects.- Parameters:
t- the pool tunnel; may be null or zero-length- Returns:
- true when the local path cannot carry anything
classifySessionLoss
static TunnelPool.SessionLoss classifySessionLoss(boolean hadSessionAtBuild, boolean establishedNow, boolean peerReachable) Attribute a lost local transport session to our side or the peer's.A zombie proves only that a session which existed at build time is gone now. It does not say why, and the two causes point at completely different fixes: our side closing idle sessions is a policy question, while a dead peer is not. This separates them without the transport needing to know anything about tunnels - the pool asks whether the peer would still answer an establishment.
Returns UNKNOWN whenever the inputs cannot support a conclusion, because attributing a loss wrongly sends the next investigation in the wrong direction, which is worse than not classifying it.
- Parameters:
hadSessionAtBuild- true when a session to the local hop existed when the tunnel was builtestablishedNow- true when a session to that hop exists nowpeerReachable- true when the peer would still answer an establishment- Returns:
- the attribution, or UNKNOWN when it cannot be determined
shouldDropForLostSession
static boolean shouldDropForLostSession(long firstMissingAt, long now, long hysteresis) Whether a tunnel whose local session is gone should be dropped from the pool.Excluding a zombie from the health counts is not enough: it stays in the pool, occupying a slot that carries nothing, until the normal pruning walk happens to remove it. Only tunnels never stamped with a first-seen-session timestamp are kept, since there is no evidence of how long the session has been missing and dropping them would be a guess.
- Parameters:
firstMissingAt- when the session was first seen gone, in ms, or 0 if never recordednow- current time in mshysteresis- how long the session must stay missing before the drop- Returns:
- true when the tunnel should be removed
stampLostSession
static long stampLostSession(long currentMissingAt, long now) The value to record for a tunnel whose local transport session has just been observed missing. Pure decision helper for unit tests.The existing timestamp survives every later pass. Resetting it would restart the hysteresis clock each time a tunnel was looked at, so a session that never came back would never accumulate enough duration to be dropped — the opposite of what the drop exists to do.
- Parameters:
currentMissingAt- the timestamp already recorded, or 0 when this is the first pass to noticenow- current time in ms- Returns:
- the timestamp to store
stampLostSession
Record that this tunnel's local transport session was seen missing, keeping the first observation as the start of the duration.- Parameters:
t- the tunnel; must not be nullnow- current time in ms
clearLostSession
Clear the missing-session timestamp when the session is back, so a tunnel that reconnects does not carry a duration towards its next loss.- Parameters:
t- the tunnel; must not be null
dropLostSessionTunnels
private int dropLostSessionTunnels()Remove tunnels whose local transport session has been gone long enough that keeping them costs more than rebuilding.A zombie occupies a slot that every health count has to exclude on its way past, and it stays in the pool until the ordinary non-good prune removes it. Dropping it after the hysteresis frees the slot for a build that can actually carry traffic.
Two protections keep this from being aggressive, because a session loss is often transient: the tunnel must have been without its session for the full hysteresis, and it must pass the same
isPrunableNonGood(TunnelInfo, boolean)test the ordinary prune applies — so a tunnel that is still GOOD and still published in a LeaseSet is never removed here, however long its session has been gone. Removing one of those would break client connections during the propagation window, which is exactly the hazard that prune documents.- Returns:
- the number of tunnels dropped
zombieTransportBucket
Normalise a RouterInfo transport style into one of the known transports.A zombie is a tunnel whose transport session died after the build completed, so peer selection is not the cause and cannot fix it: the candidate had a session when the build succeeded. What does point at the cause is which transport lost that session, and RouterInfo carries the style as a free-form string, so the values published into the netdb are folded here before they are used as a stat name.
- Parameters:
style- the style from the peer's RouterAddress, or null when the peer has no address we can read- Returns:
- NTCP, NTCP2, SSU, SSU2, or unknown
countZombieTransport
Attribute one zombie exclusion to the transport its local hop used.Counted per exclusion rather than per warning so the split survives the warning's rate limit. The style is read from the peer's first published address, which is an approximation: a peer publishing several transports is counted under whichever appears first, so a skewed split towards one transport should be read as a lead rather than a measurement.
- Parameters:
t- the zombie tunnel whose local hop session was lost- Returns:
- the transport bucket the exclusion was counted under
localHopTransportBucket
The transport a tunnel's local hop uses, folded to one of the known names.Shared by the zombie split and the all-hops denominator so both read the same address the same way. That matters more than it looks: the style is taken from the peer's first published address, which is an arbitrary choice for a peer advertising several transports. Applying the same arbitrary choice to both sides means the bias cancels in the ratio instead of compounding.
- Parameters:
t- the pool tunnel; may be null- Returns:
- one of NTCP, NTCP2, SSU, SSU2, or unknown
countLocalHopTransports
Count the local hop's transport for every tunnel in the walk.This is the denominator the zombie split is read against. A proportion without its base rate is unfalsifiable: 98% of zombies being NTCP2 means the transport is implicated only if NTCP2 is *not* 98% of tunnels generally. Read this count beside
tunnel.poolZombieTransport.*over the same window.The population is exactly the tunnels this health walk could see - unexpired and not already failed or untestable - which is the population the zombie exclusions are drawn from. Both walks count it over their own population, so each contributes a consistent pair.
- Parameters:
tunnels- the tunnels the walk considered; may be null
isBackloggedNextHop
Whether the tunnel's next hop is backlogged, meaning congested.Pure enough to be a named predicate: a length-1 tunnel has no next hop to be congested, so it is never treated as backlogged. Extracted so the ensure gate and any future caller agree on what "backlogged" means for a pool tunnel.
- Parameters:
t- the pool tunnel- Returns:
- true if the next hop exists and is currently backlogged
isSoftDegraded
Whether the tunnel is soft-degraded for ensure/deficit purposes: still in the pool and not FAILING, but soft failures have reachedSOFT_DEGRADED_FOR_ENSURE.- Parameters:
t- tunnel to classify; may be null (not degraded)- Returns:
- true when soft failures indicate the tunnel cannot be trusted to carry data even though tests may still pass
isPingPool
private boolean isPingPool()Whether this is a short-lived ping pool. Ping pools keep their configured quantity — the 2-tunnel floor doesn't apply.- Returns:
- true for a pool whose destination nickname marks it as a ping
isServerPool
private boolean isServerPool()Whether this is a server (inbound, non-exploratory) pool.- Returns:
- true if the pool publishes a LeaseSet rather than serving a client
requestLeaseSurge
public void requestLeaseSurge(int count) Note how many advertiseable tunnels the imminent mint needs, sopruneExcessTunnels()can offer up the ones that are no longer advertiseable until the pool has enough. Not a build target -- see_leaseSurgeNeed.Monotonic: a later request never lowers the requirement, so a second mint asking for less cannot relax pruning that an earlier one still needs.
- Parameters:
count- how many advertiseable tunnels the imminent mint needs
clearLeaseSurge
public void clearLeaseSurge()Drop any lease-material prune override. Called once the mint is published or its window passes, so the pool returns to plain active-count upkeep.getLeaseSurgeNeed
int getLeaseSurgeNeed()The advertiseable count an in-force surge needs, or 0 when none is.- Returns:
- the number of additional tunnels the surge should build
getEffectiveTarget
int getEffectiveTarget()The tunnel count the pool builds and keeps: the ordinary active-count target, unaffected by any lease surge. The surge influences supply through pruning, not by moving this.- Returns:
- the number of tunnels to build and keep
getActiveTarget
private int getActiveTarget()The tunnel count the pool maintains, never less than 2 per direction regardless of the configured quantity, unless the pool is a ping pool or expressly zero-hop.- Returns:
- the number of tunnels to build and keep
getSettings
setSettings
Update the settings for this pool.- Parameters:
settings- the new settings, may be null
setPairedPool
Paired pool (inbound <-> outbound for same destination).- Parameters:
pool- the paired pool
getPairedPool
TunnelPool getPairedPool()Paired pool (inbound <-> outbound for same destination).- Returns:
- the paired pool, or null
isAlive
public boolean isAlive()Is this pool running AND either exploratory, or tracked by the client manager? A pool will be alive but not tracked after the client manager removes it but before all the tunnels have expired.- Returns:
- true if the pool is alive and should be tracked
size
public int size()Count every tunnel in the pool, including failed and expired ones awaiting cleanup.- Returns:
- the number of tunnels in the pool
getValidTunnelCount
public int getValidTunnelCount()Count valid (non-failed, not expired) tunnels in the pool. Includes untested and testing tunnels — suitable for exploratory pools and build management.- Returns:
- the number of valid tunnels
getActiveTunnelCount
public int getActiveTunnelCount()Count GOOD and TESTING (non-failed, not expired) tunnels in the pool. Excludes untested, failing, and failed tunnels. Includes TESTING tunnels so the pool doesn't build unnecessarily while tunnels await test results. Suitable for LeaseSet publication and UI "ready" indicators.- Returns:
- the number of active tunnels
getTestingTunnelCount
public int getTestingTunnelCount()Count tunnels that have been built but not yet passed their first test. Excludes previously-GOOD (FAILING), FAILED, and definitely-failed tunnels. These are treated as "in progress" for replacement accounting and UI display.- Returns:
- the number of testing tunnels
getInProgressCount
public int getInProgressCount()Count tunnels currently being built.- Returns:
- the number of in-progress builds
removeInProgress
Remove a config that was never actually sent to the network (e.g. a build skipped because the first-hop peer already had a build in flight). Unlike buildComplete(), no result accounting, peer cooldowns, or profile updates occur. The pool's deficit logic will simply request the tunnel again later.- Parameters:
cfg- the config to remove from the in-progress list
cancelExcessInProgress
Cancel excess in-progress tunnel builds to stay within budget. Cancels the newest builds first (less time invested).- Parameters:
maxAllowed- the maximum number of in-progress builds allowed- Returns:
- list of cancelled tunnel configs (BuildExecutor should remove from _currentlyBuildingMap)
pruneExcessTunnels
public int pruneExcessTunnels()Prune excess tunnels from the pool to stay within budget. UsesExpireJob.scheduleExpiration(RouterContext, PooledTunnelCreatorConfig)rather than direct removal.- Returns:
- number of tunnels marked for removal
pruneOverBudget
private void pruneOverBudget(List<TunnelInfo> toRemove, boolean isServerPool, long now, int currentSize, int pruneThreshold) When the pool exceeds the prune threshold, mark excess tunnels for early expiry, preferring GOOD tunnels for non-server pools. Caller must hold _tunnelsLock.- Parameters:
toRemove- list the early-expired tunnels are added toisServerPool- true for a pool that publishes a LeaseSetnow- current time in mscurrentSize- tunnels currently in the poolpruneThreshold- size above which the excess is pruned
maybePreBuildReplacement
private void maybePreBuildReplacement()When the paired opposite-direction pool is nearly empty (size <= 1), its tunnels are slow/failed and cleanup may not catch up — nudge the manager to pre-build a replacement now, throttled.isPairedPoolStrained
private boolean isPairedPoolStrained()Paired-pool gate: skip pruning entirely when this inbound pool's opposite outbound pool is below its target. Pruning IB tunnels when OB has 0 active causes a synchronized collapse: all IB tunnels built at boot expire together, all OB pools lose their paired IB tunnels simultaneously, and 5+ EMERGENCY triggers fire at once. Let tunnels expire naturally (11 min) to stagger the removal.- Returns:
- true to skip pruning
computePruneThreshold
private int computePruneThreshold(int target, boolean isServerPool) Prune threshold: the base target, relaxed when build success is poor — killing spare tunnels you can't rebuild just makes pool collapse more likely. 80%+ success prunes at target (normal), 60% or below allows 1.5x target, and between the two the multiplier interpolates linearly. Server pools always prune at target.- Parameters:
target- the ordinary active-count target for the poolisServerPool- true for a pool that publishes a LeaseSet- Returns:
- the size above which excess tunnels are pruned, never below target
markExcessTunnels
private int markExcessTunnels(List<TunnelInfo> toRemove, boolean isServerPool, int goodTarget, long now, int toPrune) Mark excess tunnels for early expiry, down to the prune threshold. Tunnels are sorted by prune rank then expiration: FAILED > FAILING/TOO_SLOW/OVER_BUDGET > UNTESTED > TESTING > GOOD, soonest- expiring first within a rank. Server pools never prune GOOD or FAILING tunnels — GOOD tunnels are published in the LeaseSet and removing them breaks client connections, and FAILING tunnels were recently GOOD, still referenced by the propagated LeaseSet; pruning during the propagation window causes unreachable destinations. Non-server pools keep at least quantity GOOD tunnels.- Parameters:
toRemove- list the early-expired tunnels are added toisServerPool- true for a pool that publishes a LeaseSet, which never prunes GOOD or FAILING tunnelsgoodTarget- how many GOOD tunnels a non-server pool must keepnow- current time in mstoPrune- how many tunnels still need pruning- Returns:
- the remaining deficit not yet pruned
isProtectedFromPruning
A tunnel is protected from pruning if it is a server-pool GOOD or FAILING tunnel, is actively carrying traffic (non-exploratory pools only. Without this carve-out, pools running live traffic never get pruned because every tunnel is recently active within the 30s window, so they accumulate unbounded), is already inside the early-expiry window, is not a pool config, or has no usable gateway tunnel ID.- Parameters:
info- the tunnel being considered for pruningisServerPool- true for a pool that publishes a LeaseSetnow- current time in ms- Returns:
- true if the tunnel must be left in the pool
scheduleEarlyExpiry
private void scheduleEarlyExpiry(PooledTunnelCreatorConfig cfg, long now, long staggerMs, boolean overBudget) Schedule an early expiry for a config; the caller records it in its removal list. ExpireJob removes the config after the window.- Parameters:
cfg- the tunnel config to expire earlynow- current time in ms, the base of the new expirationstaggerMs- additional delay to stagger removalsoverBudget- mark over budget (true) or too slow (false)
pruneExcessGoodTunnels
Fallback prune for non-server pools: when only GOOD tunnels remain to prune, mark the soonest-expiring GOOD tunnels for early expiry, skipping tunnels actively carrying traffic.- Parameters:
toRemove- list the early-expired tunnels are added totoPrune- how many tunnels still need pruningnow- current time in ms
pruneForLeaseSurge
Schedule early expiry for the least lease-material GOOD tunnels while a lease surge is active.Counts tunnels with at least
LEASE_MIN_REMAINING_MSof life left -- the same barselectLeaseTunnelsapplies -- and prunes the soonest-expiring offenders until that many advertiseable tunnels remain, or there is nothing left to give. Tunnels that areisRecentlyActive()are never pruned: they may still be carrying client data, and evicting those to gain fresher replacements would trade LeaseSet lifetime for a data path outage, which is the worse failure.Deliberately one-sided. This only ever gives up tunnels that cannot be advertised; it never raises the target itself, so a pool already stocked with fresh tunnels is left alone.
- Parameters:
toRemove- accumulates pruned tunnels, never nullleaseSurge- how many advertiseable tunnels are needednow- current time in ms
leaseSurgeCandidates
static List<TunnelInfo> leaseSurgeCandidates(List<TunnelInfo> tunnels, List<TunnelInfo> toRemove, long now) Tunnels a lease surge may schedule for early expiry: GOOD, past the lease admission floor (so they can no longer be advertised), not already being pruned, and not recently active.Split out from
pruneForLeaseSurge(List, int, long)as a pure function because this is the decision that can remove a tunnel from the pool, and it needs to be testable without standing up a populated pool. Soonest-expiry-first is applied by the caller, not here.- Parameters:
tunnels- the pool's tunnelstoRemove- already-scheduled prunes, excludednow- current time in ms- Returns:
- prunable tunnels, in pool order
countLeaseViableTunnels
How many tunnels could still be advertised right now, on the same barselectLeaseTunnelsapplies, matching the wording of the sibling prune path.- Parameters:
tunnels- the pool's tunnelstoRemove- already-scheduled prunes, excludednow- current time in ms- Returns:
- number of tunnels with lease admission remaining
markFailedTunnelsForEarlyExpiry
Mark completely failed tunnels for early expiry (all pool types). Capped so the pool keeps at least target tunnels, and staggered 15s apart so one ExpireJob batch doesn't remove them all simultaneously.- Parameters:
toRemove- list the early-expired tunnels are added totarget- the tunnel count the pool should be left withnow- current time in ms
pruneRank
Prune priority rank — lower prunes first. UNTESTED ranked last so they get tested first.- Parameters:
s- the tunnel's test status, or null- Returns:
- the rank: 0 for FAILED, 99 for UNTESTED, GOOD last of the rest
getBuildSuccessRate
private double getBuildSuccessRate()Global tunnel build success rate as a fraction (0.0-1.0). Reads the same StatManager rate the Tuner uses. Returns NaN if no data yet (early startup).- Returns:
- the build success rate
getReplacementTunnelBuffer
static int getReplacementTunnelBuffer(double buildSuccess) How many usable tunnels above the target the pool may hold before a further build must replace a non-GOOD tunnel instead of being added.A healthy pool keeps a 2-tunnel buffer (target + 2) so a tunnel lost to expiry or test failure still leaves a full target's worth of usable leases while replacements build. Under tunnel stress (build success below
ProfileOrganizer.ATTACK_THRESHOLD) the buffer drops to 1 (target + 1): replacement builds trigger sooner, so the pool regains coverage instead of hoarding near-dead tunnels, and the buffer restores itself once build success recovers.A NaN rate (no data yet, e.g. early startup) is treated as healthy — missing stats are not evidence of an attack, and the pool must not shrink its buffer before it has any statistics to act on.
- Parameters:
buildSuccess- the global build success rate as a fraction [0.0, 1.0]; NaN when the rate is not yet available- Returns:
- 2 when healthy, 1 when stressed
isStruggling
public boolean isStruggling()Whether this pool is struggling to meet its tunnel targets. Returns true if this pool can't meet its published LeaseSet target or has fewer active tunnels than the configured quantity. Used byClientPeerSelectorto relax the cross-tunnel peer diversity constraint when the pool is starved for build candidates.- Returns:
- true if the pool is struggling
addTunnel
Add a tunnel to the pool.- Parameters:
info- the tunnel to add
getGatewayTunnelId
The tunnel ID used to dedupe additions: receive side for inbound pools, send side for outbound.- Parameters:
info- the tunnel just built- Returns:
- the gateway tunnel ID, or null if the tunnel has none
isRecentlyAddedDuplicate
Check the recent-additions map for a duplicate gateway ID, cleaning stale entries so the map cannot grow unbounded.- Parameters:
info- the tunnel about to be added, named in the duplicate warninggatewayId- the gateway tunnel ID to look upnow- current time in ms, used to age out stale entries- Returns:
- true if a duplicate addition was detected (and logged)
isLongEnoughLived
Only keep tunnels that outlive the 60s add window.- Parameters:
info- the tunnel about to be addednow- current time in ms- Returns:
- true if the tunnel expires more than 60 seconds from now
addOrReplaceTunnel
private boolean addOrReplaceTunnel(TunnelInfo info, TunnelId gatewayId, long now, int usable, int maxUsable, int target) Add the tunnel unless the pool is at capacity; at capacity try replacing a non-GOOD tunnel instead of dropping the new build.- Parameters:
info- the tunnel just builtgatewayId- its gateway tunnel ID, recorded as recently addednow- current time in msusable- usable tunnels in the pool before this addmaxUsable- capacity ceiling above which the add is refusedtarget- the ordinary active-count target, named in the at-capacity log- Returns:
- false if the tunnel was dropped (pool full of GOOD tunnels)
isDuplicateInPool
Reject re-adds of the same object (buildComplete called twice) and different objects with the same gateway tunnel ID.- Parameters:
info- the tunnel about to be addedgatewayId- its gateway tunnel ID, compared against the pool's- Returns:
- true if the tunnel is already in the pool
countUsableTunnels
private int countUsableTunnels()Count tunnels that are neither FAILED nor FAILING (dead/dying must not block replacements).- Returns:
- how many pool tunnels could still carry traffic
replaceNonGoodTunnel
private boolean replaceNonGoodTunnel(TunnelInfo info, TunnelId gatewayId, long now, int usable, int maxUsable, int target) At capacity, swap the new tunnel in for the first replaceable one — only FAILED tunnels (or FAILING for non-server pools). UNTESTED and TESTING tunnels keep their slot so TestJob can score them. Replacing them before testing creates a build→replace→build churn cycle where the pool never accumulates GOOD tunnels.- Parameters:
info- the tunnel to swap ingatewayId- its gateway tunnel ID, recorded as recently addednow- current time in msusable- usable tunnels in the pool before the swapmaxUsable- capacity ceiling that put the pool at capacitytarget- the ordinary active-count target, named in the replacement log- Returns:
- true if a tunnel was replaced, false if all tunnels are GOOD
findReplacee
Find a tunnel that can be replaced: FAILED, or FAILING for non-server pools. UNTESTED and TESTING tunnels keep their slot so TestJob can score them; replacing them before testing creates a build→replace→build churn cycle where the pool never accumulates GOOD tunnels.- Returns:
- a tunnel that may be swapped out, or null if every tunnel is GOOD
swapInReplacement
Swap the replacement into the pool and record it as recently added. Caller must hold _tunnelsLock.- Parameters:
replacee- the tunnel being swapped outinfo- the tunnel taking its slotgatewayId- info's gateway tunnel ID, recorded as recently addednow- current time in ms
publishLeaseSetIfNeeded
Publish a freshly-built LeaseSet, bypassing the refresh throttle when the pool is nearly empty AND the published LeaseSet is missing or expiring soon. The first replacement tunnel must be published immediately so clients can reconnect. Never bypass on pool size alone: with a current LeaseSet, requesting on every build would create a requestLeaseSet storm.- Parameters:
info- the tunnel just added; a short-lived one is not publishedls- the freshly built LeaseSet, or null if none was builtnow- current time in ms
expireZeroHopFallbacks
private void expireZeroHopFallbacks()Expire all zero-hop tunnels in this pool immediately. Called when a multi-hop tunnel joins an exploratory pool: the zero-hop fallbacks were bootstrap placeholders, and a real replacement supersedes them. Setting expiration to now routes them through normal removal, which keeps LeaseSet lifecycle (refresh + grace) intact.removeTunnel
Remove a tunnel from the pool.- Parameters:
info- the tunnel to remove
removeTunnelSynchronous
Synchronous tunnel removal for use during recovery or critical situations. This removes the tunnel and ensures a new LeaseSet is published BEFORE returning, preventing client connection failures during recovery.- Parameters:
info- tunnel to remove- Returns:
- true if removed and LeaseSet republished, false otherwise
rebuildLeaseSetForServerPool
Build a fresh LeaseSet from the surviving tunnels after a removal. Caller must hold _tunnelsLock.- Returns:
- the rebuilt LeaseSet, or null when there are no leases
republishAfterSynchronousRemoval
Republish the rebuilt LeaseSet, or fall back to a fresh build when no LeaseSet could be built.- Parameters:
ls- the rebuilt LeaseSet, or null when none could be builtremaining- tunnels left in the pool, named in the warning
pruneZeroHopTunnels
private void pruneZeroHopTunnels()Remove any zero-hop bootstrap tunnels from the pool. Only removes tunnels with getLength() <= 1 when the pool is configured for >0 hops. Caller must hold _tunnelsLock.removeFromDispatcherAndExpiration
Remove a tunnel config from the dispatcher and the expiration queue, in that order. remove() owns the bandwidth accounting and ignores a config that was never joined, so zero-hop tunnels are safe here. Cancelling only the expiry job leaves the dispatcher holding the config, and with it the whole hop object graph, for the life of the router.- Parameters:
info- the tunnel being dropped; anything not a pool config is ignored
buildNewLeaseSetFromTunnels
private LeaseSet buildNewLeaseSetFromTunnels(List<TunnelInfo> tunnels, boolean isServerPool, boolean rotateForPropagation, List<TunnelInfo> rotatedOut) Build a LeaseSet from a list of tunnels.This is the single canonical LeaseSet construction path for this pool; all callers (periodic republish and synchronous tunnel removal) build their LeaseSet here so the inclusion rules stay consistent.
Rules:
- FAILED tunnels are never included.
- GOOD tunnels are preferred; when no GOOD tunnels exist, untested tunnels are used as a fallback.
- Tunnels expiring within 2 minutes are excluded — the LeaseSet must propagate through the floodfill network before the lease expires.
- Zero-hop tunnels (length ≤ 1) are only included when the pool is expressly configured for zero hops (length 0). Bootstrap and build stress overrides must never expose the router directly in a LeaseSet.
- When no GOOD tunnel has ever been published, publication is deferred rather than falling back to degraded tunnels.
- Parameters:
tunnels- list of tunnels to build from; the caller must not modify it concurrentlyisServerPool- whether this is an inbound non-exploratory poolrotateForPropagation- if true, rotate out the oldest lease when its end time matches the previous publication's earliest end time, so floodfill peers see wasNew=true and re-flood the LeaseSetrotatedOut- if rotation removed a tunnel from the LeaseSet, filled with that tunnel, otherwise empty; may be null when rotation is not needed- Returns:
- LeaseSet or null if not enough tunnels
hasGoodTunnel
Whether any tunnel in the list is GOOD and expiring after the propagation deadline — gates the UNTESTED fallback.- Parameters:
tunnels- the candidate tunnelsexpireAfter- the earliest expiration a tunnel may have to count- Returns:
- true if at least one GOOD tunnel can still be advertised
collectLeaseTunnels
private TunnelPool.LeaseTunnels collectLeaseTunnels(List<TunnelInfo> tunnels, long expireAfter, boolean hasGoodTunnel) Select the tunnels that qualify for a LeaseSet: GOOD tunnels (or UNTESTED fallback when no GOOD exists. Fully built and functional, just not yet verified), expiring after the propagation deadline, with the single best zero-hop tunnel kept separately. Zero-hop tunnels are only kept when the pool expressly allows them; bootstrap, build- stress, and collapse overrides must never expose the router directly.- Parameters:
tunnels- the pool's tunnelsexpireAfter- the earliest expiration a tunnel may have to be advertisedhasGoodTunnel- whether the pool holds a GOOD tunnel, which decides whether UNTESTED tunnels qualify- Returns:
- the tunnels to build the LeaseSet from, never null
isEligibleForLease
Whether the tunnel may be included in the LeaseSet: not failed, GOOD (or UNTESTED when no GOOD exists), and expiring after the propagation deadline.- Parameters:
tunnel- the tunnel to testhasGoodTunnel- whether the pool holds a GOOD tunnel, which decides whether an UNTESTED tunnel qualifiesexpireAfter- the earliest expiration the tunnel may have to qualify- Returns:
- true if the tunnel may be included in the LeaseSet
collectTrafficProvenUntested
UNTESTED tunnels that have verified delivering real inbound data recently. Inbound delivery through InboundEndpointProcessor is end-to-end proof the tunnel works, so these are safe to publish as leases even though they never ran an explicit TestJob. They top up the configured LeaseSet target when GOOD tunnels alone don't fill it, keeping a hosted destination reachable instead of exposing a thin LeaseSet. Inbound-only: dispatcing bytes outbound proves only that the gateway accepted them, not delivery.- Parameters:
tunnels- candidate tunnels to inspectexpireAfter- don't return tunnels that near their expiry so the LeaseSet can propagate before the lease lapses- Returns:
- the traffic-proven UNTESTED inbound tunnels, quality-sorted
isTrafficProvenUntestedLeaseCandidate
Whether an inbound UNTESTED tunnel has verified delivering real data recently enough to be published as a lease. Inbound delivery through InboundEndpointProcessor is end-to-end proof the tunnel works, so it is safe to publish even though it never ran an explicit TestJob. The traffic must be fresh (withinTestJob.TRAFFIC_PROOF_MS) and backed by verified bytes, not merely recent activity.- Parameters:
tunnel- the tunnel to inspect (must be inbound)expireAfter- don't accept tunnels that near expiry so the LeaseSet can propagate before the lease lapsesnow- wall-clock timestamp for the freshness check- Returns:
- true if the tunnel is eligible to publish as a traffic-proven lease
replaceZeroHopCandidate
Whether the tunnel replaces the current zero-hop candidate: it expires later. More than one zero-hop tunnel in a lease is pointless and increases the leaseset size needlessly.- Parameters:
zeroHopTunnel- the candidate in use, or null when none istunnel- the candidate offered in its place- Returns:
- true if the offered tunnel should replace the current one
hasInboundGateway
Whether the tunnel has the required inbound gateway and tunnel ID.- Parameters:
tunnel- the tunnel to check- Returns:
- true if both are present; false logs a broken-tunnel error and keeps the tunnel out of the lease set
leaseKeyOf
The identity a lease for this tunnel is keyed on: its receive tunnel id.Two tunnels sharing a receive tunnel id are the same lease, so this is what decides whether an offered tunnel adds anything.
- Parameters:
tunnel- the tunnel to identify- Returns:
- the receive tunnel id, or -1 when the tunnel has none
buildLeases
Assemble the Lease objects for the selected tunnels: the single zero-hop lease (if any), then the quality-sorted GOOD tunnels.Package-visible so the assembly can be exercised directly: the guarantee that matters here is that every tunnel offered yields a lease, and that is a property of this method rather than of the tunnels the router happened to build when the test ran.
- Parameters:
goodTunnels- the tunnels to advertise, best firstzeroHopTunnel- the zero-hop tunnel if one is in use, else null- Returns:
- the leases, ordered by the pool's lease comparator
addDegradedFallbackLease
private void addDegradedFallbackLease(TreeSet<Lease> leases, List<TunnelInfo> tunnels, boolean isServerPool) Emergency fallback: when the LeaseSet has no leases but one has been published before, add the best degraded tunnel so clients don't lose the destination.- Parameters:
leases- the set to append the fallback lease to, modified in placetunnels- the pool's current tunnels, searched for a degraded oneisServerPool- true for a server pool, which may still advertise a tunnel that has failed or failed its test
rotateOldestLeaseForPropagation
private void rotateOldestLeaseForPropagation(TreeSet<Lease> leases, List<TunnelInfo> tunnels, List<TunnelInfo> rotatedOut) Rotate out the oldest lease when its end time matches the previous publication's earliest end time, so floodfill peers see wasNew=true and propagate the LeaseSet onward. Without this, a stable tunnel set produces the same earliest end time every build and the floodfill never re-floods the LS — it slowly fades from the network.- Parameters:
leases- the assembled lease set, modified in place when a lease is rotated outtunnels- the pool's current tunnels, searched for the rotated lease's tunnelrotatedOut- collects the tunnel that was rotated out, or null to skip the bookkeeping
shouldRotateLease
private boolean shouldRotateLease(long earliest) Whether the oldest lease should be rotated out: its end time matches the previous publication's earliest end time (a stable tunnel set produces the same earliest end time every build, which would otherwise never re-flood the LeaseSet).- Parameters:
earliest- end time of the oldest lease in the assembled set, in ms, 0 or less when the lease has no usable end time- Returns:
- true if this is a repeat of the previous publication and the oldest lease should be swapped out
markRotatedOut
Record the tunnel matching the rotated lease as rotated out so the build path can exclude it from the next LeaseSet.- Parameters:
rotated- the lease that was dropped from the settunnels- the pool's current tunnels, searched by tunnel id and gatewayrotatedOut- collects the matching tunnel, modified in place
isMateriallyShort
static boolean isMateriallyShort(int leases, int wanted) Whether a published LeaseSet is too small to count as a working set.Half the target is the same line as the pool's pre-emergency threshold (
PRE_EMERGENCY_FRACTION), so the two signals agree on which tier matters instead of one covering everything and the other covering half of it.- Parameters:
leases- number of leases actually assembledwanted- the target the pool was trying to reach- Returns:
- true if the set is below half of target
usableLeaseMinimum
static int usableLeaseMinimum(int wanted) The smallest LeaseSet treated as usable for a given target.- Parameters:
wanted- the target the pool was trying to reach- Returns:
- the half-target floor, rounded up
shouldLogShortfall
private boolean shouldLogShortfall(long now) Whether this pool may report a materially short LeaseSet again.- Parameters:
now- current wall clock- Returns:
- true if the reporting interval has elapsed, and records the time
finalizeLeaseSet
processRemovalStats
Process removal statistics for removed tunnels. Profiling lifetime must match the tunnel lifetime used at build time (getTunnelLifetime), or peers of tuned pools get skewed profile data.- Parameters:
removed- list of removed tunnels
tunnelFailed
Remove tunnel and blame all peers (not necessarily equally). This may be called multiple times fromTestJob. Enhanced with intelligent failure analysis and recovery.- Parameters:
cfg- the tunnel that failed
tunnelFailedWithoutBlame
Mark a tunnel failed without blaming any peer.Called from
PooledTunnelCreatorConfig.tunnelFailedFirstHop()when a data-phase first-hop send-failure streak reaches the fatal threshold. The first-hop send-failure path records spaced, decayed send failures that are overwhelmingly local congestion (expired-on-queue, CoDel drops, no-bid replies), not a dead peer. Blaming peers here accumulates ~50 points per failure on each hop of a 3-hop tunnel — 8 consecutive failures banlists the first hop, the tunnel is removed, the pool collapses to zero usable, and the cycle repeats on the next generation. No peer is proven guilty by a send that never left us.- Parameters:
cfg- the tunnel that failed
tunnelFailed
Remove the tunnel and blame only one peer. This may be called multiple times. Data-phase first-hop send failures also feed the selection cooldown and the tier-demotion strike tracker so a persistently failing first hop is not re-selected on the next tunnel generation.- Parameters:
cfg- the tunnel that failedblamePeer- the peer to blame
blameSinglePeer
Apply the whole failure penalty to one peer: profile failure, the selection cooldown, tier demotion, and a priority retest so a persistently failing hop is not re-selected on the next generation.- Parameters:
cfg- the tunnel that failedblamePeer- the single peer to blame, may be null
reportSendFailure
Report a data-phase dispatch failure for a tunnel in this pool. Called from the I2CP dispatch path (OutboundClientMessageOneShotJob) for hard outbound-dispatch failures (status 7, 8, 9, 13) and soft send timeouts (status 3). Hard failures share the cumulative counter with TestJob and are marked once it exceedsTunnelCreatorConfig.MAX_CONSECUTIVE_TEST_FAILURES. Soft timeouts increment only the soft counter so congestion cannot trip getTunnelFailed()/passesScanGates at the hard bar; they are marked only aboveSOFT_REMOVAL_THRESHOLD. Either way the tunnel is retained in the pool rather than dropped — seefailWithCount(TunnelInfo, int).- Parameters:
cfg- the tunnel that carried the failed messagestatus- the I2CP MessageStatusMessage failure code
describeHops
Name the hops of a failed tunnel so a data-phase failure is attributable.TunnelInfo#toString()identifies the tunnel only by nickname, which for a client tunnel is the destination nickname — so every soft failure in a pool looked identical and there was no way to tell whether one peer was failing repeatedly or the failures were spread across the pool. Hop 0 is the peer adjacent to us, so a peer recurring at the same hop across many failures is the one to penalise.- Parameters:
cfg- the tunnel that failed, may be null- Returns:
- a
* Hops: [a] -> [b]segment, empty when no hops are known
tunnelLabel
Identify a tunnel by its owning destination's base32 prefix. The pool prefix of the calling messages already carries the nickname, and the full config toString would repeat it and restate a failure count the message has just incremented.- Returns:
- "Tunnel [base32-8]" for client pools, "Tunnel" for exploratory
shouldBypassEnsureThrottle
static boolean shouldBypassEnsureThrottle(boolean soft, int failures, boolean overBar) Whether a data-phase failure signal is strong enough to run the ensure body immediately instead of through the throttle gate. Pure decision helper: hard failures and bar-crossing always change pool composition — the tunnel is marked and a replacement starts building. A soft timeout only does so at the degraded-bar crossing, ahead of the marking bar. Below that bar a status-3 flood is congestion, not evidence that the pool needs rebuilding now.- Parameters:
soft- true when the reported status was a soft send timeoutfailures- the failure count just recordedoverBar- true if this failure crossed the tunnel's removal bar- Returns:
- true to bypass the ensure throttle
isSoftSendFailure
static boolean isSoftSendFailure(int status) Whether the status is a soft best-effort send timeout (3). Mirrors OutboundClientMessageOneShotJob.isSoftSendFailure so the pool can pick the higher removal bar without importing the job class.- Parameters:
status- the I2CP MessageStatusMessage failure code- Returns:
- true for soft send timeouts only
exceedsRemovalThreshold
static boolean exceedsRemovalThreshold(int failures) Whether cumulative data-phase and TestJob failures exceed the removal threshold. Hard failures remove when the count is strictly greater thanTunnelCreatorConfig.MAX_CONSECUTIVE_TEST_FAILURES; soft send timeouts remove only aboveSOFT_REMOVAL_THRESHOLD.- Parameters:
failures- hard (test+data) or soft failure count- Returns:
- true if the tunnel should be removed from the pool
exceedsRemovalThreshold
static boolean exceedsRemovalThreshold(int failures, boolean soft) Whether the given failure class exceeds its marking bar.- Parameters:
failures- hard (test+data) or soft failure countsoft- true when the reporting status was a soft send timeout- Returns:
- true if the tunnel should be marked and retained rather than left selectable
exceedsSoftRemovalThreshold
public static boolean exceedsSoftRemovalThreshold(int failures) Whether a soft failure count crosses the soft marking bar. Exposed so a test can assert that a saturated soft streak still condemns, without duplicating the threshold.- Parameters:
failures- soft failure count- Returns:
- true if the tunnel should be marked and retained
softRemovalThreshold
public static int softRemovalThreshold()The soft failure count that marks a tunnel for retention. Exposed so a test can assert that the soft streak ceiling sits above the bar it exists to cross.- Returns:
- the soft removal bar
publishesLeaseSet
private boolean publishesLeaseSet()Does this pool publish a LeaseSet to the network? Structural: inbound + non-exploratory. Additionally, for I2CP pools, the session'si2cp.dontPublishLeaseSetoption can suppress publication.- Returns:
- true if this pool's tunnels are published as a LeaseSet, which requires a server pool with a known destination whose session does not set
i2cp.dontPublishLeaseSet
fail
Marks a tunnel as failed and schedules its replacement.- Parameters:
cfg- the failed tunnel
alreadyCondemned
Whether a failure report is a repeat of a condemnation already made.A condemned tunnel is retained until it expires and is replaced immediately, so it keeps receiving work and keeps reporting failures for its whole remaining lifetime. Re-processing those reports only re-logs, asks the pool for a replacement it already has, and lets the failure counter of a retained tunnel climb without bound.
The test is "would
fail()change anything", not "is this the terminal state". A soft-only cascade marks the tunnel FAILING and leaves it there; requiring the hard counter to be tripped as well meant a retained-but-soft-failing tunnel was re-condemned on every subsequent status-3 timeout, and the observed effect was one WARN per failure with the count running past a thousand on a single tunnel inside an hour.The one case that must still proceed is the escalation from a soft-only FAILING tunnel to a hard FAILED one, because that is the report which actually removes the tunnel from selection.
- Parameters:
isDead- whether the tunnel's hard failure counter is trippedstatus- the tunnel's current test status, may be null- Returns:
- true when this report adds nothing
failWithCount
Mark a failing tunnel for retention using the count that triggered the decision. Soft removals pass the soft counter while getConsecutiveFailures() may still be 0. Fail() would no-op on the hard counter alone.The tunnel is marked, not dropped:
passesScanGates(TunnelInfo, long, boolean)and the usable counts already exclude FAILING and FAILED tunnels, so it stops receiving work and stops holding a good slot, whileaddTunnel(TunnelInfo)keeps building replacements against the usable count rather than the raw pool size. The tunnel then either carries real traffic —clearFailingOnTraffic()promotes it back to GOOD once the traffic marker is fresh — or ages out with the rest of its generation.- Parameters:
cfg- the tunnel that failedfailures- the count that exceeded the removal bar
forceTunnelFailure
Force a tunnel to fail immediately and trigger a replacement build. Used when rotation is saturated and the pool has no viable alternative tunnels. This bypasses the incremental failure counter and marks the tunnel conclusively dead, so the pool builds a replacement without waiting for multiple failure reports.- Parameters:
cfg- the tunnel to force-fail
scheduleDeferredLeaseSetRepublish
private void scheduleDeferredLeaseSetRepublish()Defer LeaseSet republish so rapid consecutive failures in server pools are batched into a single LeaseSet update. Uses a boolean gate so that multiple failures within the 10s debounce window only trigger one republish.tellProfileFailed
Blame all peers in tunnel, with a probability inversely related to tunnel length Enhanced with intelligent blame distribution and recovery consideration. Also schedules priority peer tests so failed peers are quickly identified and evicted from fast/high capacity tiers.- Parameters:
cfg- the failed tunnel
computeBlamePct
Compute the blame percentage for a peer in a failed tunnel. Inbound peers in longer tunnels get doubled blame at the entry peer and halved elsewhere; heavy failure streaks reduce the blame by 25% with a 15% floor.- Parameters:
cfg- the failed tunnel being blamedi- the hop index of the peer to blame, 0-basedlen- the tunnel's hop count including this router, at least 2start- the first blamable hop index: 1 for an outbound tunnel, which skips our own gateway, 0 for an inbound oneconsecutiveFailures- the pool's consecutive build-timeout count, above 6 of which the blame is cut by 25%- Returns:
- the blame percentage handed to ProfileManager, 15-200
logPeerBlame
private void logPeerBlame(Hash peer, int pct, long uptime, long startupTime, int consecutiveFailures) Log the blame assignment for a peer, gated on startup being complete, warn logging being enabled and an active failure streak.- Parameters:
peer- the peer being blamed, may be null for a short tunnelpct- the blame percentage from computeBlamePctuptime- the router's uptime in msstartupTime- the router's startup time in ms, ms since the epochconsecutiveFailures- the pool's consecutive build-timeout count; below 6 nothing is logged
collectPeerForPriorityTest
private List<Hash> collectPeerForPriorityTest(List<Hash> peersToTest, Hash peer, int peersInTunnel, long startupTime) Collect a peer for priority testing, capped at 3 distinct peers. The sooner bad peers are retested, the sooner they drop from fast/high capacity tiers.- Parameters:
peersToTest- the running list, or null before the first collectpeer- the peer to queue, skipped when nullpeersInTunnel- the number of blamable peers in the tunnel, used only to size the liststartupTime- the router's startup time in ms; no peer is queued before that moment has passed- Returns:
- the updated list, or null when nothing has been queued yet
schedulePriorityPeerTests
updateRate
private void updateRate()Recomputes the pool's bandwidth and lifetime rates.refreshLeaseSet
void refreshLeaseSet()No-op for outbound and exploratory pools.proactiveRepublishIfHealthy
void proactiveRepublishIfHealthy()Proactively republish the LeaseSet when all tunnels are healthy (all > 5 min expiry). This ensures the LeaseSet always has fresh lifetime and prevents orphaned LeaseSets where the LeaseSet is technically valid but all leases have expired.Unlike
refreshLeaseSet()which republishes when tunnels are EXPIRING SOON, this republishes when tunnels are ALL HEALTHY to reset the LeaseSet lifetime.Called for every alive pool on every build loop iteration, so the cheap checks (throttle timestamp, then usable count) run before anything that takes the pool-wide tunnel lock.
countAllHealthyTunnels
private int countAllHealthyTunnels(long now, long expiryThreshold) Count unexpired tunnels when all of them expire beyond the given threshold.Applies the same zombie exclusion as
getHealthyTunnelCount()andgetUsableTunnelCount(), but deliberately records no rate stat: this is a yes/no probe for the lease republish decision, and it can return early with 0 as soon as it meets a near-expiry tunnel, so its total is not a settled count and a rate built from it would mix partial walks with complete ones. The two settled counts above are where the zombie total belongs.- Parameters:
now- the current time in ms, from the router's clockexpiryThreshold- ms since the epoch; a tunnel expiring before this count makes the whole probe return 0- Returns:
- the number of unexpired tunnels, or 0 if any unexpired tunnel expires within the threshold
refreshLeaseSet
public void refreshLeaseSet(boolean force) Refresh the LeaseSet, throttled to prevent flooding but not on initial creation. Public so the netdb facade can nudge local-dest refresh on accessLeaseSet.- Parameters:
force- if true, bypass throttle (for critical refresh when below minimum or near expiry)
resolveRefreshForce
Resolve whether this refresh must bypass the throttle, or defer it. When the published LeaseSet is missing (already expired) or will expire before the deferred refresh fires, throttling would leave the network with a stale/expired LeaseSet for the rest of the throttle window — force the refresh now instead so the new LeaseSet is pushed to the republish queue immediately. lookupLeaseSetLocally() returns null once the published LeaseSet expires, so null means it is stale.Note:
shouldDeferRefresh(long)has the side effect of scheduling a deferred refresh, so it must only run when throttled and not already forced.- Parameters:
now- current time in msforce- caller-supplied force flag- Returns:
- null to defer this refresh, otherwise the effective force flag
publishRefreshedLeaseSet
private void publishRefreshedLeaseSet(long now) Build the current LeaseSet from the pool and publish it, pruning tunnels that were dropped from the LeaseSet.- Parameters:
now- current time in ms
isRefreshThrottled
private boolean isRefreshThrottled(long now) Whether the refresh throttle window is still active.- Parameters:
now- the current time in ms, from the router's clock- Returns:
- true if the last refresh is younger than the throttle window, so a further refresh must not be started yet
shouldDeferRefresh
private boolean shouldDeferRefresh(long now) When throttled: defer the refresh if the currently published LeaseSet is healthy and outlasts the defer window, or if NetDB has no local copy yet because the last publish is still in flight. Otherwise the caller should force the refresh now.Keyed on the earliest lease end: latest may still be far out while the first leases have already drained, which would hide a capacity loss and skip pre-emptive re-mint.
NetDB null after a recent publish is the async-store case (see refreshLeaseSet): forcing on null alone rebuilds every throttled call and storms the client with requestLeaseSet.
- Parameters:
now- the current time in ms, from the router's clock- Returns:
- true if the refresh was deferred (caller returns)
shouldDeferMissingPublished
static boolean shouldDeferMissingPublished(long lastRefreshTime, long now, long throttleMs) Pure defer decision when NetDB has no local LeaseSet: true when a publish is still within the throttle window (async store pending), so forcing would only re-request the same data.- Parameters:
lastRefreshTime- last local publish/refresh timestamp (ms)now- current router time (ms)throttleMs- refresh throttle window (ms)- Returns:
- true if the refresh should be deferred despite a null lookup
shouldSkipAsyncPublish
static boolean shouldSkipAsyncPublish(boolean hasPublishedLS, long lastRefreshTime, long now, long throttleMs) Pure skip decision for publishLeaseSetIfNeeded: NetDB still null but we published within the throttle window — treat as in-flight rather than missing, so tunnel-add does not re-request every build.- Parameters:
hasPublishedLS- whether lookupLeaseSetLocally returned a setlastRefreshTime- last local publish/refresh timestamp (ms)now- current router time (ms)throttleMs- refresh throttle window (ms)- Returns:
- true to skip the redundant requestLeaseSet
shouldDeferPublishedRefresh
static boolean shouldDeferPublishedRefresh(long earliestLeaseDate, long now, long throttleMs) Pure earliest-keyed defer decision: the published LeaseSet still has full capacity (every remaining lease outlasts the throttle window), so this refresh may be deferred without losing reachability.- Parameters:
earliestLeaseDate- earliest lease end of the published LeaseSet (ms)now- current router time (ms)throttleMs- refresh throttle window (ms)- Returns:
- true if the refresh may be deferred
scheduleDeferredRefresh
private void scheduleDeferredRefresh()Schedule a deferred LeaseSet refresh to fire after the throttle window expires. Prevents dropping refresh requests during rapid tunnel changes.isExpiringSoon
boolean isExpiringSoon(long now) Check if the LeaseSet is expiring soon (within 1 minute).- Parameters:
now- milliseconds since the epoch, fromClock.now()- Returns:
- true if refresh should be forced
getInboundTunnelsAsLeaseSet
Build and return current LeaseSet from our tunnels. Used byTunnelPoolManagerto validate NetDB has current data and by RepublishLeaseSetJob to re-mint local-only (unpublished) LeaseSets.- Returns:
- current LeaseSet or null
notifyServerPoolTestFailed
void notifyServerPoolTestFailed()Called by TestJob when a server pool tunnel fails a test but is retained. Schedules a deferred republish to batch any additional failures within the 10s debounce window rather than publishing an emergency LeaseSet on every single failure.requestLeaseSet
Request lease set from client for the primary and all aliases.- Parameters:
ls- non-null
requestLeaseSet
Request LeaseSet with optional force bypass of throttle.- Parameters:
ls- non-nullforce- if true, bypass throttle
buildFallback
boolean buildFallback()This will build a fallback (zero-hop) tunnel ONLY if this pool is exploratory, or the settings allow it. Matches upstream: client pools with zero-hop disabled must never fall back to 0-hop, since such a tunnel would be selected for data and would fail every connection through it.- Returns:
- true if a fallback tunnel is built, false otherwise
buildBootstrapTunnels
boolean buildBootstrapTunnels()Build bootstrap tunnels for exploratory pools during cold start. Unlike buildFallback(), this builds 2-hop tunnels (not zero-hop) to provide usable reply paths for client tunnel builds. Called by BootstrapPool during startup until the pool has sufficient tunnels.- Returns:
- true if a bootstrap tunnel build was initiated, false otherwise
locked_buildNewLeaseSet
Build a leaseSet with the required tunnels that aren't about to expire. Caller must hold _tunnelsLock. The returned LeaseSet will be incomplete; it will not have the destination set and will not be signed. Only the leases will be included.- Returns:
- null on failure
isCachedLeaseSetValid
Whether the cached LeaseSet can be reused. Tunnels have an 11-minute lifetime; a cached LS within 5 min is still valid and prevents unnecessary churn in published leases. Emergency callers (removeTunnelSynchronous) use buildNewLeaseSetFromTunnels and bypass this cache entirely. CHECK: if the cached LS is expiring within 3 min, bypass cache regardless of age — the caller needs fresh lease dates, not recycled ones. CHECK: if the last LS was incomplete (fewer leases than wanted), rebuild so new GOOD tunnels are included promptly.- Parameters:
cached- the cached LeaseSet, or nullnow- the current time in ms, from the router's clock- Returns:
- true if the cached LeaseSet may be reused instead of rebuilding one
removeRotatedOutTunnel
Remove a rotated-out tunnel from the pool. Do NOT removeFromExpiration() here: the ExpireJob Phase 2 (dispatcher cleanup) still needs to fire naturally to avoid orphaning the tunnel in the dispatcher routing table.- Parameters:
rotated- the tunnel rotated out of the LeaseSet
getTunnelAvgLatency
Average test latency for a tunnel.- Parameters:
t- the tunnel to query- Returns:
- average test latency in ms for the tunnel, or -1 if unknown
pruneNonGoodTunnels
private void pruneNonGoodTunnels()Remove non-GOOD tunnels from the pool when enough GOOD ones remain. This prevents FAILING tunnel accumulation that stallsgetActiveTunnelCount()and prevents the pool from building replacements via ensureSufficientTunnels().pruneOverReserve
Prune the non-GOOD tunnels down to the LS fallback reserve, keeping the best (fewest failures), then refresh the published LeaseSet.- Parameters:
toRemove- the prunable tunnels, non-emptygoodCount- count of GOOD/FAILING tunnels in the pool
isPrunableNonGood
True for tunnels that should be pruned: FAILED/FAILING/unknown-status or failed in test. Server pools keep GOOD and FAILING — GOOD are in the published LeaseSet, FAILING were recently GOOD and the old propagated LeaseSet still references them.- Parameters:
t- the tunnel under considerationisServerPool- true for a pool that publishes a LeaseSet, which keeps GOOD and FAILING tunnels- Returns:
- true if the tunnel should be pruned from the pool
computeNonGoodReserve
LS fallback reserve to keep among the pruned candidates. Publishing pools keep up to (target - good) low-failure tunnels so the LeaseSet never goes empty; non-publishing pools keep none — dead tunnels just inflatesize()and block addTunnel/replacement. Use the base target, NOT effectiveTarget — dynamic scaling must not inflate the reserve or pools full of broken tunnels can never recover. Never keep zombies (>5 consecutive failures) as fallback — they're useless and block pool recovery.- Parameters:
toRemove- the non-GOOD candidates, scanned for non-zombie tunnelsgoodCount- the GOOD tunnels currently in the pool- Returns:
- how many of toRemove to keep as LeaseSet fallback, 0 for a non-publishing pool
keepBestReserve
Keep the reserve tunnels with the fewest failures, drop the worsttoPrune.- Parameters:
toRemove- the candidates, sorted in place by consecutive failurestoPrune- how many of the worst candidates to drop- Returns:
- the tunnels left in the reserve, fewest failures first
pruneNonPublishedTunnels
After publishing a LeaseSet, prune GOOD tunnels that weren't included in the published LeaseSet. These tunnels will just expire unused — they consume slots and their IP-based LeaseSet presence can't be used because the LeaseSet referencing them has already been published. Only applies to inbound server pools that publish LeaseSets.- Parameters:
publishedLS- the LeaseSet that was just published, skipped when null
collectPublishedGateways
collectNonPublishedTunnels
private List<TunnelInfo> collectNonPublishedTunnels(Set<Hash> publishedGateways, int latencyThreshold, long now) Scan for GOOD tunnels not included in the published LeaseSet. These will just expire unused — they consume slots and their LeaseSet presence can't be used because the referencing LeaseSet is already published. Freshly built tunnels stay as reserve whatever their latency — measurements aren't stable yet and they're the best candidates for the next LeaseSet. Don't prune UNTESTED tunnels — they were just built; pruning before testing creates a build→prune→build churn cycle where the pool can never accumulate enough GOOD tunnels.- Parameters:
publishedGateways- the gateways named by the published LeaseSetlatencyThreshold- ms above which a non-published tunnel is too slow to keepnow- the current time in ms, from the router's clock- Returns:
- the tunnels to prune, empty when every candidate is published or fresh
isPruneCandidate
private boolean isPruneCandidate(TunnelInfo t, Set<Hash> publishedGateways, long now, int latencyThreshold) Whether the tunnel should be pruned: near expiry or slower than the latency threshold, not fresh, not UNTESTED, and not published in the current LeaseSet.- Parameters:
t- the tunnel under considerationpublishedGateways- the gateways named by the published LeaseSetnow- the current time in ms, from the router's clocklatencyThreshold- ms above which the tunnel counts as too slow to keep- Returns:
- true if the tunnel should be pruned
wouldDropBelowTarget
private boolean wouldDropBelowTarget(int currentSize, int removeCount) Collapse guard: skip the prune when it would drop the pool below its effective target (base + emergency boost).- Parameters:
currentSize- the pool's usable tunnel count before the pruneremoveCount- how many tunnels the prune would remove- Returns:
- true to skip
removeTunnelsBatch
Remove tunnels from the pool in one batch under the lock, then update stats outside the lock. Batch removal avoids calling removeTunnel() per tunnel, which would trigger ensureSufficientTunnels() for each one and create recursive build storms. ExpireJobs are NOT canceled — the 2-phase lifecycle must complete so the tunnel stays in the dispatcher for the full LEASESET_GRACE_PERIOD after pool removal, giving clients with cached LeaseSets time to transition.- Parameters:
toRemove- the tunnels to take out of the pool, not modified
computeLeaseEndDate
static long computeLeaseEndDate(long tunnelExpiration, long now, long maxLeaseMs) Compute the end date advertised for a lease, given the tunnel's own expiration. Pure so the lifetime/grace relationship is testable without a live tunnel: the lease must end before the gateway stops routing, and must never be advertised beyond the configured maximum.The three steps are, in order: subtract
LEASE_SAFETY_MARGINso peers re-fetch while the gateway still routes; clamp tonow + maxLeaseMsso nobody caches us for the full lifetime; and clamp up tonow + 60sso a nearly-dead tunnel still yields a usable lease rather than one already expired. That last floor is what a thin pool hits, and it is why leases were observed living ~89s: the pool had no fresher tunnel to offer, not because the lifetime was wrong.- Parameters:
tunnelExpiration- when the tunnel stops routing, msnow- current time in msmaxLeaseMs- maximum advertised lease duration from now, ms- Returns:
- the lease end date, always greater than
now
buildLeaseFromTunnel
Build a Lease from a single tunnel's gateway. The lease endsLEASE_SAFETY_MARGINbefore the tunnel expires so peers re-fetch while the gateway still routes.The end date is bounded by the shorter of the tunnel's overall expiration (shortened when the tunnel is scheduled for early expiry, e.g. pruned excess) and the gateway hop's original full lifetime. A tunnel scheduled for early expiry stops routing when the shortened expiration fires, so a lease outliving that would leave the destination unreachable until the LeaseSet is republished.
- Parameters:
cfg- the tunnel to advertise- Returns:
- the lease for its inbound gateway, or null when the tunnel has no receive tunnel id or no gateway
findBestDegradedTunnel
Find the best degraded tunnel from a given list for emergency LS fallback. Picks the tunnel with the fewest consecutive failures (excluding fully dead tunnels). If tied, picks the one with the latest expiration. For server pools (inbound non-exploratory), tunnels retained with getTunnelFailed() == true are still eligible — they're deliberately kept to prevent pool collapse and must be available for LS fallback. Zero-hop tunnels are never eligible unless the pool is expressly configured for zero hops (length 0).Only tunnels with at least
LEASE_MIN_REMAINING_MSremaining are eligible: the lease endsLEASE_SAFETY_MARGINbefore the tunnel expires and is floored 60s out, so a tunnel closer to death would produce a lease ending after the gateway stops routing.- Parameters:
tunnels- list to search; the caller must not modify it concurrentlyisServerPool- if true, don't skip getTunnelFailed() tunnels- Returns:
- best candidate or null
isEligibleDegradedCandidate
Whether the tunnel may be replaced as a degraded candidate.- Parameters:
t- the tunnel under considerationisServerPool- true for a server pool, which may still use a tunnel marked failednow- the current time in ms, from the router's clock- Returns:
- true if the tunnel can serve as the emergency fallback lease
isBetterDegradedCandidate
private boolean isBetterDegradedCandidate(TunnelInfo best, TunnelInfo t, int bestFailures, int failures) Whether the tunnel beats the current best: fewer consecutive failures, or equal failures with a later expiration.- Parameters:
best- the current best candidate, null on the first passt- the tunnel compared against itbestFailures- the consecutive-failure count of the current bestfailures- the consecutive-failure count of t- Returns:
- true if t should take over as the best degraded candidate
getLifetimeProcessed
public long getLifetimeProcessed()Total lifetime processed bytes for this pool.- Returns:
- the total number of bytes processed through this pool
configureNewTunnel
PooledTunnelCreatorConfig configureNewTunnel()This only sets the peers and creation/expiration times in the configuration. For the crypto, seeBuildRequestorand BuildMessageGenerator.- Returns:
- null on failure
countHealthySafe
Safe tunnels that are not soft-degraded. Soft-degraded tunnels are excluded from the healthy count used for deficit throttle collapse detection so a pool of test-passing but data-phase-failing tunnels uses the short cooldown instead of the 30s healthy gate.- Parameters:
stats- sweep counters for one ensure pass- Returns:
- healthy (non soft-degraded) safe-active tunnel count
computeDifficultySpare
static int computeDifficultySpare(int consecutiveBuildTimeouts) Spare tunnels held above target while the pool is having difficulty building. Config-free replacement for a static backupQuantity: difficulty is tracked with the existing consecutive build-timeout counter (reset on any successful or answered build), so the reserve grows only while builds are silently timing out and decays as soon as builds complete again.- Parameters:
consecutiveBuildTimeouts- consecutive silent build timeouts since the last successful or answered build- Returns:
- 0 with no sustained difficulty, 1 after
SPARE_LOW_THRESHOLDtimeouts, 2 afterSPARE_HIGH_THRESHOLD
getDifficultySpare
int getDifficultySpare()Current difficulty spare for this pool.- Returns:
- spare tunnels to hold above target, 0-2
rotateWindowStart
static long rotateWindowStart(long windowStart, long now, long windowMs) Roll the build-rate window forward when it has expired.- Parameters:
windowStart- start of the current window (ms), 0 if nonenow- current router time (ms)windowMs- window length (ms)- Returns:
- start time to use for the next count —
nowwhen the window is missing or expired, otherwise unchanged
timeoutRate
static double timeoutRate(long attempts, long timeouts) Fraction of counted build attempts that timed out.- Parameters:
attempts- build attempts in the window, may be 0timeouts- timed-out attempts in the window- Returns:
- rate clamped to [0.0, 1.0]; 0.0 when there were no attempts
computePreBuildWindowMs
static long computePreBuildWindowMs(double rate) How far ahead of expiry to start building replacements, scaled by how badly this pool's builds are failing: a pool with a high build-timeout rate needs more lead time because each attempt may burn a full timeout (17-30s) before the next one starts. Missing or invalid data (NaN) keeps the base window. Absent statistics are not evidence of failure.recordBuildOutcome
private void recordBuildOutcome(boolean timedOut, long now) Record one build outcome in the fixed-width rate window. Only results where a build actually went to the network count — local skips (NO_TUNNELS, NO_NETDB, SKIPPED, OTHER_FAILURE) are not evidence of peer timeout behavior.- Parameters:
timedOut- true if this attempt timed outnow- current router time (ms)
getBuildTimeoutRate
private double getBuildTimeoutRate()This pool's build-timeout rate over the current window.- Returns:
- rate in [0.0, 1.0]; 0.0 when the window is empty or stale (no builds within
BUILD_RATE_WINDOW_MS)
computeEffectiveTarget
private int computeEffectiveTarget(int target) Effective build target: base target plus the dynamic emergency boost, the Tuner's failure buffer, and a reserve. The reserve is the larger of the struggle reserve (pool below target or thin LeaseSet) and the difficulty spare (sustained build timeouts) — the two signals describe the same "keep headroom" need, so they never stack.- Parameters:
target- the base tunnel count from the pool's configuration- Returns:
- how many tunnels the pool should try to hold: base target plus the emergency boost, the Tuner's failure buffer and a reserve
getNearExpiryCount
public int getNearExpiryCount()Tunnels in this pool whose lease falls inside the pre-build window.- Returns:
- count of near-expiry live tunnels as of the last sweep
sweepExpiredAndCountTunnels
Drops expired tunnels and counts the rest.- Parameters:
now- the current timepreBuildThreshold- cutoff for the pre-build decision- Returns:
- the counts for the pool's tunnels
addStrandedTest
Collect a tunnel for the stranded re-offer, bounded byTestJob.MAX_STRANDED_OFFERS_PER_SWEEPper sweep.- Parameters:
stats- counters from the running sweepcfg- the tunnel to collect
removeTunnelFromPool
Remove a tunnel from the pool and, if it is a build config, from the dispatcher and the expiry map as well. Cancelling only the expiry job leaves the dispatcher holding the config, and with it the whole hop object graph, for the life of the router.- Parameters:
it- the iterator over the pool's tunnel list, advanced past the tunnelt- the tunnel being removed
handleStaleUntested
private void handleStaleUntested(Iterator<TunnelInfo> it, TunnelInfo t, TunnelPool.TunnelStats stats, long now, long wallNow, long preBuildThreshold) Handle an UNTESTED tunnel: a tunnel still UNTESTED within the pre-build window (expiring in < 3 min) is stuck — the test queue never reached it (saturated) or it was abandoned after a pool reset. If it still has enough life for one test round trip, keep it and queue that last-chance test (the sweep schedules it outside the lock) instead of discarding an unproven lease; otherwise prune it, unless it has recently carried verified traffic. A kept tunnel counts as near-expiring: it still triggers deficit replacements and remains usable until it expires or the test resolves it. A tunnel with more than the pre-build window of life is also collected for the stranded re-offer — it must not wait for expiry to get its first test.- Parameters:
it- the iterator over the pool's tunnels, advanced past the tunnel when it is prunedt- the UNTESTED tunnel under considerationstats- the sweep's counters, updated with the outcomenow- the current time in ms, from the router's clockwallNow- the current wall-clock time in ms, compared against the tunnel's last transfer timestamppreBuildThreshold- the ms timestamp ending the pre-build window: a tunnel expiring before it needs a replacement build now
isLastChanceTestable
static boolean isLastChanceTestable(long expiration, long now, long minLifeMs) Whether a tunnel expiring within the pre-build window still has enough life left for a last-chance test to mean anything.- Parameters:
expiration- tunnel expiration (router clock, ms)now- current router time (ms)minLifeMs- minimum remaining life for the test to be worthwhile- Returns:
- true when at least
minLifeMsof life remains
scheduleLastChanceTests
Offer one last-chance test for each stale-but-testable UNTESTED tunnel the sweep kept. Called outside the pool lock; the candidates go to the batched first-test pump (TestJob.offerFirstTest(RouterContext, PooledTunnelCreatorConfig, TunnelPool)), which dedupes by tunnel key and applies the in-flight gates at drain time, so repeated ensure passes cannot double-queue and a denied offer is retried on the next sweep.- Parameters:
stats- the sweep's counters, whose lastChanceTests list holds the candidates
offerStrandedTests
Offer tunnels that have never produced a latency reading. These are UNTESTED tunnels whose first test never got scheduled (the queued cap dropped the build-time offer or the pool was saturated), and tunnels promoted to GOOD by traffic proof alone, which have no retest chain to inherit. Called outside the pool lock; offer-time dedupe (buffer keys and the running-test registry) makes repeated sweeps idempotent, and the pump's in-flight gates bound how many dispatch per cycle. Without this, the sweep only counted them: stranded tunnels stayed UNTESTED until expiry while their untestedCount held replacement builds back, and traffic-promoted tunnels were never measured at all.- Parameters:
stats- counters from the sweep that collected the candidates
isProtectedInUse
Whether an UNTESTED tunnel expiring within the pre-build window is protected: it recently carried verified traffic. Protection also retires stale test bookkeeping — verified bytes are proof in either direction, same asclearFailingOnTraffic().- Parameters:
t- the UNTESTED tunnel under considerationwallNow- the current wall-clock time in ms, compared against the tunnel's last transfer timestamp- Returns:
- true if the tunnel is protected because it recently carried verified traffic, in which case its stale test bookkeeping is cleared
clearFailingOnTraffic
public void clearFailingOnTraffic()Clear FAILING/FAILED flags on tunnels that have provably carried real traffic recently. Data that actually moved through the tunnel is stronger evidence than a tunnel test, so the tunnel has not failed and must stay selectable and in the pool. A test failure on such a tunnel is a reply-path false negative.Proof is direction-neutral: InboundEndpointProcessor stamps only after the tunnel's crypto verified, and OutboundClientMessageOneShotJob's SendSuccessJob stamps only after the remote peer replied to the message. Neither dispatch nor a bare gateway accept reaches either call site, so the old inbound-only restriction no longer describes what the marker means.
FAILED (3+ consecutive failures) is dead and is NOT cleared here. A traffic stamp proves one packet got through, which is not evidence against three consecutive failed tests; only a real passing test (or a rebuild) returns a dead tunnel to service. FAILING (1-2 failures) is transient by definition and clears on a single proof. A genuinely dead tunnel stamps neither marker, so it stays FAILING/FAILED, is excluded from
getUsableTunnelCount()andgetHealthyTunnelCount()while marked, and ages out at natural expiry — retention cannot strand it as usable.UNTESTED tunnels that have delivered verified bytes are promoted to GOOD outright as well. A tunnel that received real end-to-end traffic is proven to work — waiting for a TestJob that may never be scheduled (the fresh tunnel can sit UNTESTED until expiry while the pool keeps rebuilding replacements) means the published LeaseSet stays thin even though usable tunnels are standing. Promoting proof-of-traffic UNTESTED tunnels lets LeaseSet building see them immediately. Verified-bytes is required for UNTESTED (not just recency) because such a tunnel has never passed an explicit test, so its only proof is actual data arrival.
Called from the periodic pool maintenance sweep (~15s).
countLiveTunnel
Count a live non-UNTESTED tunnel: safe when it outlives the pre-build window, else near expiry.- Parameters:
t- the tunnel to classifystats- the sweep's counters, incremented for the tunnelpreBuildThreshold- the ms timestamp ending the pre-build window: a tunnel expiring after it counts as safe
logCleanupSummary
Warn once per cycle when zombie or stale untested tunnels were removed.- Parameters:
stats- the sweep's counters holding the cleanup tallies
decayEmergencyCounter
private void decayEmergencyCounter(int safeActive, int effectiveTarget, int target) Decay the collapse counter: reset when stable or empty (a high counter on an empty pool inflates the target and hoards broken tunnels), else decrement by one per cycle once back at the base target.- Parameters:
safeActive- the count of safe, non soft-degraded tunnels this cycleeffectiveTarget- the boosted target including the emergency countertarget- the base tunnel count from the pool's configuration
shouldSkipDueToInProgress
private boolean shouldSkipDueToInProgress(int safeActive, int target, int inProgress) Cap concurrent builds to prevent build storms: partial pools may run up to 2x target (capped at 6) so timed-out constructions are replaced without waiting for the slot. Healthy pools stay at target + 1. Collapsed pools (zero safe tunnels) usemin(target+2, 8)so recovery is faster than the old hard cap ofMAX_BUILD_PER_POOL_DIRwhile still bounding the buildComplete → ensureSufficientTunnels feedback loop.- Parameters:
safeActive- the count of safe, non soft-degraded tunnels in the pooltarget- the base tunnel count from the pool's configurationinProgress- the builds currently in flight- Returns:
- true to skip the build cycle
shouldSkipDueToInProgress
private boolean shouldSkipDueToInProgress(int safeActive, int target, int inProgress, double timeoutRate) Variant taking the build timeout rate, which the hot path already has.A thin pool's in-flight budget has to account for builds that will never complete. Each failed attempt occupies a slot for the whole timeout — 17 to 33 seconds. So at a 50% timeout rate a budget of six yields roughly three landable tunnels per timeout rather than six, and the pool replaces fewer tunnels per cycle than it loses while it still drains. Measured on a live router at 47% build success with timeouts at 51%: pre-emergency fired 10,620 times across five outbound pools while several sat at zero outbound tunnels.
Scaling the budget up as the timeout rate rises gives a failing pool room for enough concurrent attempts to land a replacement inside a single timeout window, which is the only way a pool with a high failure rate can recover at all. It is bounded by
MAX_INPROGRESS_PER_POOL_DIR, so the spend stays capped.- Parameters:
safeActive- the count of safe, non soft-degraded tunnels in the pooltarget- the base tunnel count from the pool's configurationinProgress- the builds currently in flighttimeoutRate- fraction of builds timing out in [0.0, 1.0]- Returns:
- true if the in-flight budget is exhausted
shouldSkipDueToDedup
private boolean shouldSkipDueToDedup(int inProgress, int safeActive, long now) Dedup guard: skip the cycle when concurrent events (prune, expire, removal) all trigger within 5s. Builds take 10-40s and duplicate batches waste capacity. Bypassed on total collapse (safeActive == 0), where emergency builds must proceed immediately.- Parameters:
inProgress- the builds currently in flightsafeActive- the count of safe tunnels, 0 during a total collapsenow- the current time in ms, from the router's clock- Returns:
- true to skip the build cycle
buildDeficitReplacements
private void buildDeficitReplacements(TunnelPool.TunnelStats stats, int target, int effectiveTarget, long now) Proactive replacement: when safeActive is below effectiveTarget and tunnels are expiring within the pre-build window (or the LeaseSet is incomplete), build the deficit now so replacements are ready before the old tunnels die — preventing synchronized expiry cascades. Untested tunnels don't count as deficit unless the pool has zero GOOD tunnels, so waiting for test results doesn't churn the test queue. Failing tunnels count toward the deficit (capped at base target); per-cycle builds are capped at base target and respect pool backoff unless the pool is fully collapsed.- Parameters:
stats- the sweep's counters for this cycletarget- the base tunnel count from the pool's configurationeffectiveTarget- the boosted target including the emergency counternow- the current time in ms, from the router's clock
isDeficitThrottled
static boolean isDeficitThrottled(long now, long lastBuild, int healthySafe, int safeActive, int inProgress, boolean incompleteLeaseSet) Whether a deficit-build batch must wait. Pure decision helper so throttle policy is unit-testable without a router context.Policy:
DEFICIT_THROTTLE_MS(30s) when the pool still has healthy usable tunnels.DEFICIT_DEGRADED_MS(10s) while the published LeaseSet is incomplete, or the pool is soft-degraded- dominated (safeActive > 0 but healthySafe == 0);DEFICIT_COLLAPSE_COOLDOWN_MS(5s) only when safeActive is truly empty, or <= 1 with nothing in flight — so recovery stays prompt but repeated fast failures cannot hammer selectSingleHop. An empty pool uses the short cooldown even with builds in flight: hung builds (17-30s timeouts) must not pin an empty pool on the 30s healthy gate. Concurrent builds stay capped by shouldSkipDueToInProgress(). A zerolastBuild(never built) is never throttled.- Parameters:
now- current router time (ms)lastBuild- last deficit-build timestamp (ms), 0 if neverhealthySafe- healthy (non soft-degraded) safe tunnel countsafeActive- all safe tunnels including soft-degradedinProgress- builds currently in flightincompleteLeaseSet- true when the pool cannot publish a full LeaseSet or is soft-degraded-dominated- Returns:
- true if the deficit build should be skipped this cycle
isDeficitThrottled
static boolean isDeficitThrottled(long now, long lastBuild, int safeActive, int inProgress, boolean incompleteLeaseSet) Compatibility overload: callers that only have a single usable count (no soft-degraded split) treat it as both healthy and safe.- Parameters:
now- current router time (ms)lastBuild- last deficit-build timestamp (ms), 0 if neversafeActive- all safe tunnels including soft-degradedinProgress- builds currently in flightincompleteLeaseSet- true when the pool cannot publish a full LeaseSet- Returns:
- true if the deficit build should be skipped this cycle
isEnsureThrottled
static boolean isEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, boolean incompleteLeaseSet) Whether ensureSufficientTunnels() must wait for this pool. Pure decision helper for unit tests.Policy:
ENSURE_THROTTLE_MS(15s) normally;ENSURE_DEGRADED_MS(5s) while the published LeaseSet is incomplete (thin reachability), or when some usable tunnels are soft-degraded but the pool still has healthy capacity;ENSURE_COLLAPSED_MIN_MS(2s) floor when the pool is collapsed (usable tunnels <= 1) or healthy (non soft-degraded) tunnels <= 1 — a pool of soft-degraded tunnels looks full to getUsableTunnelCount() while failing every data-phase send, so the long gate would starve replacements during a half-open outage.Compatibility overload: callers that only have a single usable count treat it as both usable and healthy. See
isEnsureThrottled(long, long, int, int, boolean)for the full policy.- Parameters:
now- current router time (ms)lastEnsure- last ensure timestamp (ms), 0 if neverusableTunnelCount- current usable tunnel count (includes soft-degraded)incompleteLeaseSet- true when the pool cannot publish a full LeaseSet- Returns:
- true if ensureSufficientTunnels() should return early
isEnsureThrottled
static boolean isEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, int healthyCount, boolean incompleteLeaseSet) Whether ensureSufficientTunnels() must wait for this pool, using separate usable and healthy counts. Pure decision helper.- Parameters:
now- current router time (ms)lastEnsure- last ensure timestamp (ms), 0 if neverusableTunnelCount- current usable tunnel count (includes soft-degraded)healthyCount- usable tunnels belowSOFT_DEGRADED_FOR_ENSUREincompleteLeaseSet- true when the pool cannot publish a full LeaseSet- Returns:
- true if ensureSufficientTunnels() should return early
isEnsureThrottled
static boolean isEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, int healthyCount, boolean incompleteLeaseSet, long lastRemoval) Whether ensureSufficientTunnels() must wait for this pool, using separate usable and healthy counts and a removal-time signal. Pure decision helper.Policy:
ENSURE_THROTTLE_MS(15s) normally;ENSURE_DEGRADED_MS(5s) while the published LeaseSet is incomplete (thin reachability), or when some usable tunnels are soft-degraded but the pool still has healthy capacity;ENSURE_COLLAPSED_MIN_MS(2s) floor when the pool is collapsed (usable tunnels <= 1), healthy (non soft-degraded) tunnels <= 1, or a tunnel was removed since the last ensure run — a removal is a liveness signal that must not wait behind the healthy gate.- Parameters:
now- current router time (ms)lastEnsure- last ensure timestamp (ms), 0 if neverusableTunnelCount- current usable tunnel count (includes soft-degraded)healthyCount- usable tunnels belowSOFT_DEGRADED_FOR_ENSUREincompleteLeaseSet- true when the pool cannot publish a full LeaseSetlastRemoval- last tunnel-removal timestamp (ms), 0 if never- Returns:
- true if ensureSufficientTunnels() should return early
isEnsureThrottled
static boolean isEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, int healthyCount, boolean incompleteLeaseSet, long lastRemoval, int inProgress) Whether ensureSufficientTunnels() must wait for this pool, using separate usable and healthy counts, a removal-time signal, and the number of builds in flight. Pure decision helper.Policy:
ENSURE_THROTTLE_MS(15s) normally;ENSURE_DEGRADED_MS(5s) while the published LeaseSet is incomplete (thin reachability), or when some usable tunnels are soft-degraded but the pool still has healthy capacity;ENSURE_COLLAPSED_MIN_MS(2s) floor when the pool is collapsed (usable tunnels <= 1), healthy (non soft-degraded) tunnels <= 1, or a tunnel was removed since the last ensure run — a removal is a liveness signal that must not wait behind the healthy gate. A fully empty pool with no builds in flight bypasses the gate entirely: there is nothing to rate-limit (the deficit and emergency throttles still pace the builds themselves), and waiting even 2s on the way to an empty pool extends the outage for no benefit.- Parameters:
now- current router time (ms)lastEnsure- last ensure timestamp (ms), 0 if neverusableTunnelCount- current usable tunnel count (includes soft-degraded)healthyCount- usable tunnels belowSOFT_DEGRADED_FOR_ENSUREincompleteLeaseSet- true when the pool cannot publish a full LeaseSetlastRemoval- last tunnel-removal timestamp (ms), 0 if neverinProgress- builds currently in flight, or -1 when unknown- Returns:
- true if ensureSufficientTunnels() should return early
isDeficitBuildNeeded
Whether a deficit build is warranted: some tunnels expiring soon, none safe, soft-degraded presence below healthy capacity, or an incomplete LeaseSet that needs more GOOD tunnels — but not when the incomplete-LeaseSet trigger is already satisfied by the current healthy good+untested count. Soft-degraded presence also warrants a deficit build even when safeActive looks full: those tunnels pass tests yet fail data-phase sends, so replacements must stage before the soft removal bar rotates them out.- Parameters:
stats- sweep counters for this ensure passeffectiveTarget- base target plus emergency/failure boost- Returns:
- true when a deficit replacement batch should be considered
computeDeficit
private int computeDeficit(TunnelPool.TunnelStats stats, int target, int effectiveTarget, int currentInProgress) Compute the build deficit. Failing tunnels will likely die soon — count them as deficit so replacements build before the pool drains. Zero healthy-safe tunnels: soft-degraded tunnels still occupy safe slots, so only the shortfall to effectiveTarget is built (not a full empty-pool rebuild that overfills then thrash-trims). When safeActive is also empty, in-progress builds haven't produced usable tunnels yet (~40s build+test) and expiring ones die first, so only build the gap — bounded via Math.max so timeouts can't create a build → timeout → build storm, and untested tunnels count against the deficit so builds don't pile up faster than the test queue can process them.- Parameters:
stats- sweep counters for this ensure passtarget- base effective tunnel counteffectiveTarget- target plus emergency/failure boostcurrentInProgress- builds currently in flight- Returns:
- number of replacement tunnels to request this cycle
computeDeficit
static int computeDeficit(int healthySafe, int safeActive, int softDegraded, int failingCount, int untestedCount, int target, int effectiveTarget, int currentInProgress) Pure deficit calculation so unit tests can pin the soft-degraded occupancy rules without a router context.- Parameters:
healthySafe- non soft-degraded safe tunnelssafeActive- all safe tunnels (includes soft-degraded)softDegraded- safe tunnels at or aboveSOFT_DEGRADED_FOR_ENSUREfailingCount- hard-failing tunnels (not in safeActive)untestedCount- untested tunnels counted against the deficittarget- base effective tunnel counteffectiveTarget- target plus emergency/failure boostcurrentInProgress- builds currently in flight- Returns:
- non-negative replacement count for this cycle
shouldSkipDueToBackoff
private boolean shouldSkipDueToBackoff(int needed, boolean collapsed) Skip proactive builds while the pool's build executor is in backoff, unless the pool is truly collapsed and recovery must proceed.- Parameters:
needed- how many tunnels the deficit calculation wants this cyclecollapsed- true when the pool has no usable tunnels at all- Returns:
- true to skip the proactive builds while the executor is in backoff
relaxWindowForStarvation
Raise the peer-selection activity window to its ceiling because a pool has no usable tunnels, and publish the new value.Deliberately not a reset: the multiplier only ever increases here, so a transient collapse does not immediately withdraw the widened window from pools that are still starved. Recovery is the Tuner's job, and its decay is now asymmetric (three consecutive healthy cycles) precisely so that a single recovered reading does not snap the window back.
- Parameters:
ctx- supplies the peer profile organizer and the Tuner's peer supply count
handleEmergencyIfNeeded
EMERGENCY rebuild: fires only when zero usable tunnels remain (no safe, none expiring, none untested). Never blocked by pool backoff, spaced by a cooldown to prevent death spirals, boosted per collapse, and gated against the paired pool so one direction can't hoard builds (unless both are empty, or neither would recover).- Parameters:
stats- the sweep's counters for this cycletarget- the base tunnel count from the pool's configuration
isEmergencySituation
Whether the pool is in the emergency condition: zero safe, expiring and untested tunnels, no still-valid (soft-degraded / FAILING) tunnels left to ride out, and not a ping pool. Soft-degraded tunnels remain valid forgetValidTunnelCount()— treating a soft-degraded pool as fully dead forced a collapse rebuild every cycle while the tunnels were still routing (or about to rotate out).- Parameters:
stats- sweep counters for this ensure pass- Returns:
- true when an emergency rebuild is warranted
hasValidTunnelsBlockingEmergency
static boolean hasValidTunnelsBlockingEmergency(int validCount) Emergency collapse is deferred while valid tunnels remain (soft-degraded or FAILING-but-not-FAILED). Pure helper for unit tests.- Parameters:
validCount-getValidTunnelCount()at decision time- Returns:
- true when valid tunnels are present, so do not fire EMERGENCY
snapshotPoolState
private long[] snapshotPoolState()Snapshot of pool build state taken under a consistent lock order (_tunnelsLock, then the _inProgress monitor). No other path nests the two locks, so taking them in this order cannot deadlock.- Returns:
- long[] {usableTunnelCount, inProgressCount} as of the snapshot
isEmergencyCooldownActive
static boolean isEmergencyCooldownActive(long elapsedMs, long usableCount, long inProgressCount) Whether the emergency-cooldown window has elapsed. Uses a shorter cooldown during total collapse: when a pool has zero usable tunnels and nothing being built, the standard cooldown lefts it stranded (the first EMERGENCY build takes ~20s to timeout, so with the standard 30s cooldown the next attempt is 50s later, during which all connections fail).- Parameters:
elapsedMs- time since the last emergency build started, in msusableCount- usable tunnel count at snapshot timeinProgressCount- in-progress build count at snapshot time- Returns:
- true if the cooldown has NOT yet elapsed, so the next emergency build must wait
isEmergencyCooldownActive
private boolean isEmergencyCooldownActive()Whether an emergency build ran within the cooldown window. When the cooldown has elapsed, records the new emergency build time.- Returns:
- true if an emergency build ran recently enough that this one must wait; false also stamps the new emergency build time
computeEmergencyBoost
private int computeEmergencyBoost(int target) Boost the build target on repeated collapses, capped at MAX_EMERGENCY_BOOST. Mutates _consecutiveEmergencies.- Parameters:
target- the base tunnel count from the pool's configuration- Returns:
- the boosted effective target, at most target + MAX_EMERGENCY_BOOST
shouldSkipDueToPairedPool
IB/OB balance: don't emergency-build if this direction already has MORE usable tunnels than its paired direction. When both pools are at zero usable, both must build — skipping both would deadlock recovery. Uses getUsableTunnelCount() (not size()) so zombie tunnels with hundreds of failures don't block recovery.- Parameters:
stats- the sweep's counters for this cycle- Returns:
- true to skip the emergency build because the paired direction is worse off
ensureSufficientTunnels
void ensureSufficientTunnels()Ensure the pool has at least target valid tunnels, building replacements proactively when the count drops below target. This prevents the pool from silently draining to zero, avoiding tunnel collapse cascades.ensureSufficientTunnelsNow
private void ensureSufficientTunnelsNow(long nowEnsure) Run the ensure body without the throttle gate. Callers that have a liveness signal (data-phase failure, fresh build) bypass the gate so replacements are not delayed behind a healthy-pool interval while soft-degraded tunnels still inflate getUsableTunnelCount().- Parameters:
nowEnsure- router time to stamp as the last ensure run
requestFreshTunnelBuild
public void requestFreshTunnelBuild()Request new inbound tunnels so the pool holds the target count with at leastgetLeaseViabilityWindow(RouterContext)remaining. Called by the LeaseSet republisher before a re-mint: the normal build logic replaces tunnels only when below target or inside its proactive replacement window (BuildExecutor starts 330s out), so a pool at target with aging leases never rebuilds on its own, and a re-mint would re-sign the same near-expired leases. Respects the in-progress cap, the per-period dedup guard and pool backoff to avoid build storms.isFreshBuildEligible
private boolean isFreshBuildEligible()Fresh builds only apply to live, non-exploratory inbound pools.- Returns:
- true if this pool may start fresh replacement builds for lease re-mint
countFreshTunnels
private int countFreshTunnels(long freshUntil) Count tunnels whose lifetime still covers the lease viability window and which have not failed.- Parameters:
freshUntil- expiration cutoff for a tunnel to count as fresh- Returns:
- the number of fresh tunnels
computeFreshBuildCap
private int computeFreshBuildCap(int target) Cap on concurrent builds for fresh replacement: at least 4, at most 6, scaled up for high targets.- Parameters:
target- the base tunnel count from the pool's configuration- Returns:
- how many fresh replacements may be built at once, 4-6
shouldDedupeFreshBuild
private boolean shouldDedupeFreshBuild(long now) Dedup: skip if a deficit build ran within the last 5 seconds.- Parameters:
now- the current time in ms, from the router's clock- Returns:
- true to skip this fresh build
isPoolInBackoff
private boolean isPoolInBackoff()Skip while the pool's build executor is in backoff.- Returns:
- true if the executor is holding this pool in backoff
buildReplacementTunnels
private void buildReplacementTunnels(int needed, boolean bypassPacing) Build the requested number of replacement tunnels, skipping any configuration failures. Capped atMAX_BUILD_PER_POOL_DIRin-flight builds to prevent the feedback loop where failed builds re-trigger more builds without limit.- Parameters:
needed- how many replacement tunnels to startbypassPacing- if true, mark each build as emergency recovery so it bypasses the executor's per-peer in-flight guard — used when the pool has zero usable tunnels and every build must go out now
configureNewTunnel
This only sets the peers and creation/expiration times in the configuration. For the crypto, see BuildRequestor and BuildMessageGenerator.- Parameters:
forceZeroHop- if true, force a zero-hop tunnel- Returns:
- the configured tunnel, or null on failure
configureNewTunnel
Configure a new tunnel, optionally excluding peers from first-hop selection. The exclusion set is used by the build executor's dispatch loop to ensure concurrent builds in the same batch target diverse first-hop peers — stacking multiple build requests on one peer floods it and makes the first build answer slower.- Parameters:
forceZeroHop- true to force a zero-hop tunnelexcludeFirstHops- first-hop peers already selected by concurrent builds in the same dispatch batch- Returns:
- the tunnel config, or null if none could be configured
expirationWithStagger
private long expirationWithStagger(long now) Expiration for a new tunnel, staggered to prevent all tunnels expiring simultaneously. With an 11-min lifetime, un-staggered tunnels across many pools all expire at ~11 min, causing ExpireJob.phase1 to remove them all in one batch → mass EMERGENCY triggers → build storm → death spiral. The stagger gives builds time to complete before the next pool's tunnels expire. Lifetime plus stagger must stay under the NetDb's 15-minute future- lease rejection boundary (MAX_LEASE_FUTURE); the cap leaves a 15s margin below it and the stagger no-ops entirely when the configured lifetime alone reaches the boundary.- Parameters:
now- the current time in ms, from the router's clock- Returns:
- the tunnel's expiration in ms: nominal lifetime plus a random stagger of up to maxExpirationStagger
maxExpirationStagger
static long maxExpirationStagger(long lifetimeMs) Upper bound for the expiration stagger: keeps lifetime plus stagger under the NetDb's 15-minute future-lease rejection boundary with a 15-second margin, and never exceeds 4 minutes.- Parameters:
lifetimeMs- the configured tunnel lifetime in ms- Returns:
- the maximum stagger in ms, 0 when the lifetime alone reaches the boundary
findReusableTunnel
Try to reuse an existing tunnel of the right length that is expiring soon, so its peers carry into the new build. Skip candidates whose peers are on cooldown to ensure diversity, and record cooldown for reused peers so selectPeers respects them.- Parameters:
len- desired length including us (endpoint first)now- the current time in ms, used to record cooldowns for reused peerscooldownCutoff- ms since the epoch: a peer whose cooldown ends after this timestamp blocks the reuse- Returns:
- the peer list to reuse, or null to fall through to normal selection
findReusableCandidateLocked
findReusableCandidate(List, int, long, int)with the pool lock held, so the scan sees a stable tunnel list.- Parameters:
len- desired length including us (endpoint first)now- the current time in ms, from the router's clockidx- how many earlier candidates to skip, 0 on the first try- Returns:
- the candidate tunnel to reuse, or null when none is eligible
hasAllPeerRIs
Whether every non-self peer in the list still has a RouterInfo cached locally. Presence-only: uses the unvalidated lookup so a background revalidation does not fail the check, matching the build requestor's acceptance of unvalidated entries.- Parameters:
peers- peer list to check, endpoint first, may include ourselves- Returns:
- true if all non-self peers have a cached RouterInfo
findReusedPredecessor
The active tunnel whose peer set matches the given newly built tunnel (matched by exact gateway-first peer-list equality), or null. Callers retire it on the successor's successful build so identical peer sets never coexist in the pool.- Parameters:
successor- the tunnel just added to the pool, non-null- Returns:
- the predecessor tunnel info, or null
findReusableCandidate
Scan the tunnel list from the given index for a reusable tunnel.Does not mark the tunnel reused. The caller rejects some candidates after the scan - peers on cooldown, or RouterInfos that have left the local netdb - and marking during the scan left every rejected candidate permanently excluded from reuse, and invisible to
findReusedPredecessor(TunnelInfo), which only matches reused tunnels.- Parameters:
tunnels- the pool's current tunnels, gateway firstlen- minimum acceptable length, including ourselvesnow- current time, for the expiry windowidx- index to start scanning at- Returns:
- the first eligible tunnel, or null if none remains
peersEndpointFirst
The tunnel's peers, endpoint first, the order selectPeers() returns them in.- Parameters:
ti- the tunnel whose peers are wanted- Returns:
- a new list of the tunnel's peers, endpoint first, this router included
hasPeerOnCooldown
Whether any non-self peer in the list is on cooldown.- Parameters:
peers- the candidate peer list, endpoint firstcooldownCutoff- ms since the epoch: a peer selected after this timestamp counts as still cooling down- Returns:
- true if any peer other than this router is still on cooldown
recordPeerCooldowns
Record a diversity cooldown for every reused peer so selectPeers respects them on the next selection. Healthy reused peers are cooled down too. The point is to spread reuse across the pool, and peers still inside their first-hop failure window are excluded anyway.- Parameters:
peers- the reused peers, endpoint first, this router skippednow- the current time in ms, recorded as each peer's selection time
logSelectPeersFailure
Warn when the peer selector found nothing for a new tunnel, naming the destination. Rate-limited: every event is logged at debug, one WARN at most per interval per pool, silent in the first 3 minutes of uptime while peers are still being discovered.- Parameters:
settings- the settings of the pool that could not select peers, used for the destination name in the message
noPeersSelectedLine
The entry naming the pool whose peer selection came up empty, so the reason a new tunnel could not be built is visible without a debug log.- Parameters:
destName- the destination nickname, or the bracketed hash truncationinbound- true to name an inbound pool, false for an outbound one- Returns:
- the log entry, never null
shouldWarnSelectPeersFailure
static boolean shouldWarnSelectPeersFailure(long uptime, long now, long lastWarnTime) The selectPeers-failure WARN is rate-limited: never beforeSELECT_PEERS_FAILURE_SILENCE_MSof uptime, then at most once perSELECT_PEERS_FAILURE_WARN_INTERVAL_MSper pool.- Parameters:
uptime- the router uptime in msnow- the current time in mslastWarnTime- the last WARN time in ms- Returns:
- true if the WARN should fire
shouldLogSendFailure
static boolean shouldLogSendFailure(long now, long lastLogTime) Whether a data-phase failure or condemnation line should be logged now. Rate-limited per pool to one line perSEND_FAILURE_LOG_INTERVAL_MS. State changes are never rate limited:failWithCount(TunnelInfo, int)andtellProfileFailed(TunnelInfo)still run on every report, only the log line is suppressed.- Parameters:
now- the current time in mslastLogTime- the last log time for this stream, ms- Returns:
- true if the line should be logged
createConfig
private PooledTunnelCreatorConfig createConfig(TunnelPoolSettings settings, List<Hash> peers, long now, long expiration) Build the config with the given peers, endpoint first. cfg.getPeer() is ordered gateway first, so peers are reversed into the config slots.- Parameters:
settings- the pool's settings, for the inbound flag, destination and prioritypeers- the selected peers, endpoint firstnow- the current time in ms, written into each hop's creation timeexpiration- the expiration in ms written into every hop and the config- Returns:
- the new tunnel configuration
fastFailTbrTarget
Fast-fail for outbound tunnels: skip this build cycle when the TBR target (cfg.getPeer(1)) is not connected and not connecting — the TBR is sent directly to this peer via transport. Pre-connect so the transport has time to establish before the next attempt; handleOutboundBuild() gives connecting peers a 12s timeout, which covers the typical ~8.5s SSU2 handshake.- Parameters:
settings- the pool's settings, an inbound pool returns false immediatelycfg- the outbound config about to be dispatched, peer 1 is the TBR target- Returns:
- true when the build should be skipped (pre-connect issued)
logConfigDebug
Logs a new tunnel's config and peers at debug level.- Parameters:
cfg- the tunnel's configpeers- the chosen peers
buildComplete
Remove from the _inprogress list and call addTunnel() if result is SUCCESS. Updates consecutive build timeout count.- Parameters:
cfg- the completed tunnel configurationresult- the build result
recordPeerCooldownOnReject
Record peer cooldown on build failure so bad OBEPs/IBGWs aren't retried. Only REJECT/BAD_RESPONSE prove the far end was at fault — it answered and refused or broke the build. TIMEOUT and DUP_ID are ambiguous: no peer can be blamed. Cooldowning on them punishes healthy peers for network-wide problems and lets a mild timeout rate saturate the cooldown maps, starving peer selection (observed 247/247 client hops in cooldown on a single-destination router).- Parameters:
cfg- the failed build config, its far end is the peer to blameresult- the build outcome, only REJECT and BAD_RESPONSE record a cooldown
handleBuildResult
Handle the build result: update the consecutive timeout counter and the paired tunnel profile per outcome.- Parameters:
cfg- the build config being reportedresult- the outcome from the pool's build executor
getConsecutiveBuildTimeouts
int getConsecutiveBuildTimeouts()Count of consecutive tunnel build timeouts.- Returns:
- the number of consecutive build timeouts
incrementBuildTimeout
void incrementBuildTimeout()Increment consecutive build timeout counter. Called byBuildExecutorfor first-hop failures (OTHER_FAILURE with buildTime >= 1000ms) so the pool's build-health checks can respond to sustained first-hop failures.resetConsecutiveTimeoutsOnSuccess
private void resetConsecutiveTimeoutsOnSuccess()Reset consecutive timeout counter when tunnels are working properly. This prevents excessive backoff on firewalled routers after recovery. Only resets if we have a significant number of consecutive timeouts to avoid flapping during normal operation.updatePairedProfile
Update the paired tunnel profiles by treating the build as a tunnel test.- Parameters:
cfg- the build for this tunnel, to lookup the paired tunnelsuccess- did the paired tunnel pass the message through
shouldBlamePaired
private boolean shouldBlamePaired(boolean success) Whether the paired tunnel should be blamed for this build result.- Parameters:
success- true when this direction's build succeeded- Returns:
- false when the build failed and the pool is exploratory or suffering sustained first-hop timeouts
findPairedInPool
Look up the paired tunnel in the given pool.- Parameters:
pool- the paired pool, may be nullpairedGW- the paired tunnel's id, recorded when the build started- Returns:
- the paired config, or null when the pool or the tunnel is gone
seedPairedTunnelIfUntested
private void seedPairedTunnelIfUntested(PooledTunnelCreatorConfig cfg, PooledTunnelCreatorConfig paired) Seed an UNTESTED paired tunnel as GOOD on build success so the pool has at least one usable tunnel. Once tested (GOOD/FAILING), build RTT doesn't overwrite real results.- Parameters:
cfg- the config that just built successfully, its build time gives the RTTpaired- the paired tunnel in the other direction, seeded only while UNTESTED
isDestinationReachable
private boolean isDestinationReachable()Check if the destination is reachable by looking up its LeaseSet.- Returns:
- whether destination reachable
shouldLogNoTunnelsWarning
private boolean shouldLogNoTunnelsWarning()Suppress "no tunnels available" warning spam with rate limiting Uses adaptive suppression between 5 and 10 minutes based on failures.- Returns:
- whether log no tunnels warning
shouldAnalyzeFailure
Determine if we should perform detailed failure analysis Only analyze every Nth failure to avoid excessive processing.- Parameters:
cfg- the config whose build failed, not consulted: the decision reads the pool's consecutive-timeout counter- Returns:
- true to run the detailed analysis on this failure
analyzeFailurePattern
Analyze tunnel failure patterns to identify root causes and suggest recovery strategies.- Parameters:
cfg- the failed tunnel config, its peers are checked for repeat offenders
toString
formatPoolIdentity
Format a pool identity for log messages, combining nickname and truncated hash. Format: "nickname/hash" if nickname set, or just "hash" if not. Globally available helper for consistent log formatting across all tunnel pool code.- Parameters:
settings- the pool settings, for the nickname and destination hash- Returns:
- the nickname and truncated base32 hash, or the hash alone when unnamed