java.lang.Object
net.i2p.router.tunnel.pool.TunnelPool
Direct Known Subclasses:
AliasedTunnelPool

public class TunnelPoolextends Object
A group of tunnels for the router or a particular client, in a single direction. Public only for TunnelRenderer in router console.
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    private class 
    Event to perform deferred LeaseSet refresh after throttle window expires.
    (package private) static class 
    Always build a LeaseSet with Leases in sorted order, so that LeaseSet.equals(Object) and lease-by-lease equals() always work.
    private class 
    Republishes the lease set after a delay.
    private static class 
    Selected tunnels for a LeaseSet build: the quality-sorted GOOD (or fallback UNTESTED) tunnels and the single best zero-hop tunnel.
    (package private) static final class 
    Why a pool could not supply a tunnel, as counts rather than prose.
    private static class 
    Result of a pool scan: the chosen tunnel (or null), the backlogged fallback candidate, and the recorded last-resort tunnel.
    (package private) static enum 
    Why a tunnel's local transport session was lost.
    private static class 
    Find the tunnel with the far-end that is XOR-closest to a given hash.
    private static final class 
    Counts of tunnel states gathered in one sweep of the pool.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    private long
    Rolling average test duration for this pool, ms; 0 until the first completion is recorded.
    private double
    Test timeout multiplier for this pool, 1.0 until adapted.
    protected boolean
    Whether 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 LeaseSet
    Minimum interval between LeaseSet builds (matches getRefreshThrottle(_context)).
    private final AtomicInteger
    Build timeouts in a row, for stress detection.
    private int
    Dynamic pool scaling: when a pool repeatedly collapses (EMERGENCY fires), increase the effective tunnel count.
    protected final RouterContext
    The router context.
    private final AtomicBoolean
    Reentrancy guard for ensureSufficientTunnels().
    private boolean
    True 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 long
    When the pool was first installed.
    private boolean
    True after first LeaseSet with at least one lease was built (not necessarily GOOD).
    private boolean
    True when last built LeaseSet had fewer leases than wanted — bypass cache.
    Configs with builds in flight, guarded by the manager.
    private long
    When the last condemn warning was logged.
    private long
    Last time ensureSufficientTunnels() started a batch of builds.
    private long
    When the last emergency build pass ran.
    private long
    Throttle for ensureSufficientTunnels hot path (per pool).
    private long
    Last time buildFallback() logged its zero-hop-refusal warning, to rate-limit it.
    private long
    When the last last-resort tunnel log was written.
    private long
    Timestamp of the last successful LeaseSet build.
    private long
    Track last proactive LeaseSet publish time for rate limiting.
    private long
    Lifetime total at the last rate update.
    private long
    When the last no-tunnels warning was logged.
    private long
    Minimum interval between pre-build triggers per pool.
    private long
    Earliest lease end time of the last published LeaseSet.
    private long
    When the pool's rates were last recomputed.
    private long
    Throttle refresh — publish at most once per throttle window.
    private long
    Last time a tunnel was removed from this pool.
    private long
    When the last selectPeers failure warning was logged.
    private long
    Rate-limit stamps for the data-phase failure / condemnation log lines.
    private long
    Last time a lost-session cause was sampled.
    private long
    When this pool last reported a materially short LeaseSet
    private int
    Last healthy count emitted in a thin-pool warning, or -1 before any.
    private boolean
    Set when a lease set republish is queued.
    private int
    Lease-material target override, or 0 when none is active.
    private long
    Total lifetime of tunnels this pool has used.
    protected final Log
    The log.
    private final TunnelPoolManager
    The manager that owns this pool.
    private int
    Live tunnels whose lease is inside the pre-build window, published by the last sweep so BuildExecutor's dispatch ordering can read it without taking the pool lock.
    private long
    Wall-clock guard for the zombie warning; see ZOMBIE_LOG_INTERVAL_MS.
    private TunnelPool
    The opposite-direction pool for the same destination.
    private final TunnelPeerSelector
    Selector choosing peers for this pool's tunnels.
    private final AtomicBoolean
    Track if a deferred refresh is already scheduled.
    private long
    Last time this pool dispatched a pre-emergency burst, or 0 if it never has.
    private final String
    Stat name prefix for this pool's rates.
    private final Object
    Guards the window rotation; one lock per build completion.
    private final Map<TunnelId,Long>
    Tunnels added recently, by id, for duplicate suppression.
    private final RateStat[]
    Holds the rejected-build stat once registered.
    The pool's current settings.
    private long
    When 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 ReentrantLock
    Lock guarding the tunnel list.
    private final AtomicLong
    Build attempts in the current stats window.
    private long
    When the current stats window began.
    private final AtomicLong
    Build timeouts in the current stats window.
    private static final long
    Fixed (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, see timeoutRate(long, long) and computePreBuildWindowMs(double).
    private static final int
    If less than one success in this many, reduce length (exploratory only).
    private static final int
    Build tries override for the second consecutive failure.
    private static final int
    Build tries override for a client pool's first failure.
    private static final int
    Build tries override for a client pool's second failure.
    (package private) static final long
    Default early expiration time for pruned tunnels (120 seconds).
    private static final long
    Shorter deficit-build spacing while collapsed (zero healthy-safe tunnels, or <= 1 with nothing in flight).
    private static final long
    Intermediate deficit-build spacing while the LeaseSet is incomplete but the pool still has usable tunnels (not collapsed).
    private static final long
    Minimum interval between deficit-build batches on a non-collapsed pool.
    private static final long
    Shorter 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 long
    An emergency build pass is followed by a 30 second wait.
    private static final long
    Floor between ensureSufficientTunnels() runs when the pool is collapsed (usable tunnels <= 1).
    private static final long
    Intermediate ensure gate while the published LeaseSet is incomplete (missing required leases) but the pool is not fully collapsed.
    private static final long
    Normal 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 long
    How recently a transport must have closed an idle session for the loss to be attributed to this router.
    (package private) static final long
    Rolling average test duration below which the test window is narrowed.
    private static final long
    A tunnel counts as in use when it has carried verified traffic within this window.
    (package private) static final long
    Minimum 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 long
    Tunnels must have at least this much remaining life to be published.
    private static final long
    Lease 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 String
    Legacy alias for PROP_PRUNE_EARLY_EXPIRY, read only as a fallback.
    private static final long
    How 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 double
    Hard ceiling on the adaptive test timeout multiplier.
    (package private) static final int
    Hard cap on concurrent builds per pool direction in normal operation.
    private static final int
    Most tunnels an emergency pass may add.
    (package private) static final int
    Absolute ceiling on builds in flight per pool and direction, applied to every build path including the pre-emergency burst.
    (package private) static final int
    Cap 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 long
    Cap 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 long
    Hard ceiling on getTunnelLifetime(RouterContext): the standard 10m lifetime plus the 1m grace period other routers afford.
    private static final long
    Pre-build passes are at least 1 minute apart.
    (package private) static final long
    Base 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 int
    Builds started in one pre-emergency burst.
    private static final long
    How long a pre-emergency burst keeps its in-flight builds exempt from the global dispatch budget.
    (package private) static final double
    Pre-emergency threshold: fraction of target below which builds start immediately.
    private static final String
    Config key for the early tunnel expiry margin.
    private static final long
    Non-published tunnels with more remaining life than this are fresh — never pruned.
    private static final long
    Non-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 long
    Track recently-added tunnel IDs to prevent duplicates.
    private static final long
    Silent in the first 3 minutes of uptime: peers are still being discovered.
    private static final long
    At most one selectPeers-failure WARN per interval per pool.
    private static final long
    Minimum gap between data-phase failure log lines, per pool.
    (package private) static final double
    Priority weight for service pool builds.
    private static final long
    Interval between lost-session attribution samples.
    private static final long
    Minimum gap between repeated "not enough leases" reports from one pool.
    (package private) static final long
    Rolling average test duration above which the test window is widened.
    (package private) static final int
    Soft-failure count at which a still-usable tunnel is treated as degraded for ensure-throttle and deficit purposes.
    (package private) static final int
    Higher removal bar for soft best-effort send timeouts (status 3).
    (package private) static final int
    Consecutive build timeouts at which the spare grows to two tunnels.
    (package private) static final int
    Consecutive build timeouts at which the pool starts holding one spare tunnel above target (see computeDifficultySpare(int)).
    private static final int
    Extra 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 long
    Minimum gap between repeated "health excludes N zombie tunnels" warnings from one pool.
    (package private) static final String
    Rate stat recording how many tunnels getHealthyTunnelCount() dropped for want of a transport session to their local hop.
    (package private) static final String
    Rate stat recording how many tunnels getUsableTunnelCount() dropped for the same reason.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Tunnel pool.
  • Method Summary

    Modifier and Type
    Method
    Description
    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.
    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.
    private static void
    Collect a tunnel for the stranded re-offer, bounded by TestJob.MAX_STRANDED_OFFERS_PER_SWEEP per sweep.
    protected void
    Add a tunnel to the pool.
    (package private) static boolean
    alreadyCondemned(boolean isDead, TunnelTestStatus status)
    Whether a failure report is a repeat of a condemnation already made.
    private void
    Analyze tunnel failure patterns to identify root causes and suggest recovery strategies.
    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.
    private java.util.function.Predicate<TunnelInfo>
    Backlog predicate matching the one scanPoolForTunnel(int, long, boolean, TunnelInfo) applies.
    private void
    blameSinglePeer(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) boolean
    Build bootstrap tunnels for exploratory pools during cold start.
    (package private) void
    Remove from the _inprogress list and call addTunnel() if result is SUCCESS.
    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.
    (package private) boolean
    This will build a fallback (zero-hop) tunnel ONLY if this pool is exploratory, or the settings allow it.
    private Lease
    Build a Lease from a single tunnel's gateway.
    (package private) TreeSet<Lease>
    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 LeaseSet
    buildNewLeaseSetFromTunnels(List<TunnelInfo> tunnels, boolean isServerPool, boolean rotateForPropagation, List<TunnelInfo> rotatedOut)
    Build a LeaseSet from a list of tunnels.
    private void
    buildReplacementTunnels(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.PoolState
    classifyPoolState(List<TunnelInfo> tunnels, long now, java.util.function.Predicate<TunnelInfo> backlogged)
    Count the pool by selection outcome.
    (package private) static TunnelPool.SessionLoss
    classifySessionLoss(boolean hadSessionAtBuild, boolean establishedNow, boolean peerReachable)
    Attribute a lost local transport session to our side or the peer's.
    void
    Clear FAILING/FAILED flags on tunnels that have provably carried real traffic recently.
    void
    Drop any lease-material prune override.
    private void
    Clear the missing-session timestamp when the session is back, so a tunnel that reconnects does not carry a duration towards its next loss.
    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.
    private List<TunnelInfo>
    collectNonPublishedTunnels(Set<Hash> publishedGateways, int latencyThreshold, long now)
    Scan for GOOD tunnels not included in the published LeaseSet.
    private List<Hash>
    collectPeerForPriorityTest(List<Hash> peersToTest, Hash peer, int peersInTunnel, long startupTime)
    Collect a peer for priority testing, capped at 3 distinct peers.
    private Set<Hash>
    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 int
    Latency ascending, tunnels with no measurements sort last.
    private boolean
    Compute the ten-minute build success and shorten the override length under stress, using the thresholds and rate stats for this pool type.
    private int
    computeBlamePct(TunnelInfo cfg, int i, int len, int start, int consecutiveFailures)
    Compute the blame percentage for a peer in a failed tunnel.
    (package private) 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.
    private int
    computeDeficit(TunnelPool.TunnelStats stats, int target, int effectiveTarget, int currentInProgress)
    Compute the build deficit.
    (package private) static int
    computeDifficultySpare(int consecutiveBuildTimeouts)
    Spare tunnels held above target while the pool is having difficulty building.
    private int
    Effective build target: base target plus the dynamic emergency boost, the Tuner's failure buffer, and a reserve.
    private int
    Boost the build target on repeated collapses, capped at MAX_EMERGENCY_BOOST.
    private int
    Cap on concurrent builds for fresh replacement: at least 4, at most 6, scaled up for high targets.
    (package private) static long
    computeLeaseEndDate(long tunnelExpiration, long now, long maxLeaseMs)
    Compute the end date advertised for a lease, given the tunnel's own expiration.
    private int
    computeNonGoodReserve(List<TunnelInfo> toRemove, int goodCount)
    LS fallback reserve to keep among the pruned candidates.
    (package private) static long
    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 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.
    private String
    Display name for the rate stat: "Exploratory tunnels" for exploratory pools, otherwise the destination nickname (HTML-stripped) or the destination's base32 address.
    (package private) PooledTunnelCreatorConfig
    This only sets the peers and creation/expiration times in the configuration.
    configureNewTunnel(boolean forceZeroHop)
    This only sets the peers and creation/expiration times in the configuration.
    (package private) PooledTunnelCreatorConfig
    configureNewTunnel(boolean forceZeroHop, Set<Hash> excludeFirstHops)
    Configure a new tunnel, optionally excluding peers from first-hop selection.
    private int
    countAllHealthyTunnels(long now, long expiryThreshold)
    Count unexpired tunnels when all of them expire beyond the given threshold.
    private int
    countFreshTunnels(long freshUntil)
    Count tunnels whose lifetime still covers the lease viability window and which have not failed.
    private static int
    Safe tunnels that are not soft-degraded.
    (package private) static int
    countLeaseViableTunnels(List<TunnelInfo> tunnels, List<TunnelInfo> toRemove, long now)
    How many tunnels could still be advertised right now, on the same bar selectLeaseTunnels applies, matching the wording of the sibling prune path.
    private void
    countLiveTunnel(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 void
    Count the local hop's transport for every tunnel in the walk.
    private void
    Attribute one zombie exclusion to this router or to the peer, and count it.
    private int
    Count tunnels that are neither FAILED nor FAILING (dead/dying must not block replacements).
    (package private) int
    Count the zero-hop fallback tunnels in this pool.
    private String
    Attribute one zombie exclusion to the transport its local hop used.
    createConfig(TunnelPoolSettings settings, List<Hash> peers, long now, long expiration)
    Build the config with the given peers, endpoint first.
    private void
    Register the pool's bandwidth rate stat.
    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.
    private static String
    Name the hops of a failed tunnel so a data-phase failure is attributable.
    private int
    Remove tunnels whose local transport session has been gone long enough that keeping them costs more than rebuilding.
    (package private) void
    Ensure the pool has at least target valid tunnels, building replacements proactively when the count drops below target.
    private void
    Run the ensure body without the throttle gate.
    (package private) static boolean
    Whether cumulative data-phase and TestJob failures exceed the removal threshold.
    (package private) static boolean
    exceedsRemovalThreshold(int failures, boolean soft)
    Whether the given failure class exceeds its marking bar.
    static boolean
    Whether a soft failure count crosses the soft marking bar.
    private long
    Expiration for a new tunnel, staggered to prevent all tunnels expiring simultaneously.
    private void
    Expire all zero-hop tunnels in this pool immediately.
    private void
    Marks a tunnel as failed and schedules its replacement.
    private void
    failWithCount(TunnelInfo cfg, int failures)
    Mark a failing tunnel for retention using the count that triggered the decision.
    private boolean
    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 LeaseSet
    finalizeLeaseSet(TreeSet<Lease> leases, int wanted, long now)
    Builds the lease set from the chosen leases.
    private TunnelInfo
    findBestDegradedTunnel(List<TunnelInfo> tunnels, boolean isServerPool)
    Find the best degraded tunnel from a given list for emergency LS fallback.
    private TunnelInfo
    findClosestTunnel(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.
    Look up the paired tunnel in the given pool.
    private TunnelInfo
    Find a tunnel that can be replaced: FAILED, or FAILING for non-server pools.
    (package private) static TunnelInfo
    findReusableCandidate(List<TunnelInfo> tunnels, int len, long now, int idx)
    Scan the tunnel list from the given index for a reusable tunnel.
    private TunnelInfo
    findReusableCandidateLocked(int len, long now, int idx)
    findReusableCandidate(List, int, long, int) with the pool lock held, so the scan sees a stable tunnel list.
    private List<Hash>
    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 TunnelInfo
    The active tunnel whose peer set matches the given newly built tunnel (matched by exact gateway-first peer-list equality), or null.
    void
    Force a tunnel to fail immediately and trigger a replacement build.
    static String
    Format a pool identity for log messages, combining nickname and truncated hash.
    private int
    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.
    int
    Count GOOD and TESTING (non-failed, not expired) tunnels in the pool.
    (package private) long
    This pool's rolling average of completed test durations, used to adapt the test timeout multiplier.
    (package private) long
    getAdaptiveTestTimeout(long baseTimeout)
    Adapt a base test timeout to this pool's observed test speed.
    int
    Average bandwidth per tunnel in the pool.
    private double
    Global tunnel build success rate as a fraction (0.0-1.0).
    private double
    This pool's build-timeout rate over the current window.
    (package private) static int
    Quantity override threshold from config or default (12).
    (package private) int
    Count of consecutive tunnel build timeouts.
    (package private) int
    Current difficulty spare for this pool.
    (package private) int
    The tunnel count the pool builds and keeps: the ordinary active-count target, unaffected by any lease surge.
    private TunnelId
    The tunnel ID used to dedupe additions: receive side for inbound pools, send side for outbound.
    (package private) int
    Usable tunnels that are not soft-degraded: same filters as getUsableTunnelCount() plus a floor on TunnelInfo.getSoftFailures().
    Build and return current LeaseSet from our tunnels.
    int
    Count tunnels currently being built.
    (package private) static long
    Maximum lease set lease duration from config or default (10 minutes).
    (package private) static long
    LeaseSet build minimum interval from config or default (2 minutes).
    (package private) int
    The advertiseable count an in-force surge needs, or 0 when none is.
    static long
    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.
    long
    Total lifetime processed bytes for this pool.
    (package private) static int
    Max concurrent builds per direction from config or default (6).
    int
    Tunnels in this pool whose lease falls inside the pre-build window.
    (package private) TunnelPool
    Paired pool (inbound <-> outbound for same destination).
    (package private) long
    Early expiry time for pruned tunnels.
    (package private) static long
    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.
    RateStat name for the bandwidth graph.
    private RateStat
    getRateStat(RateStat[] slot, String name)
    Get a rate stat, caching the handle so per-build lookups skip the StatManager lookup.
    (package private) static long
    Refresh throttle interval from config or default (2 minutes).
    (package private) 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.
    Pool settings.
    (package private) static long
    Startup suppression period from config or default (5 minutes).
    int
    Count tunnels that have been built but not yet passed their first test.
    getTunnel(TunnelId gatewayId)
    Tunnel by its gateway tunnel ID.
    (package private) static int
    Average test latency for a tunnel.
    static int
    Tunnel lifetime from config or default (11 minutes).
    TunnelPoolManager that owns this pool.
    (package private) int
    Count tunnels that are usable for routing — not expired, not failed, not marked FAILING.
    int
    Count valid (non-failed, not expired) tunnels in the pool.
    private TunnelInfo
    Handle a pass whose chosen tunnel was null but which recorded a backlogged candidate: warn and fall back to it.
    private void
    Handle the build result: update the consecutive timeout counter and the paired tunnel profile per outcome.
    private void
    EMERGENCY rebuild: fires only when zero usable tunnels remain (no safe, none expiring, none untested).
    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.
    private boolean
    Whether every non-self peer in the list still has a RouterInfo cached locally.
    private boolean
    hasGoodTunnel(List<TunnelInfo> tunnels, long expireAfter)
    Whether any tunnel in the list is GOOD and expiring after the propagation deadline — gates the UNTESTED fallback.
    private boolean
    Whether the tunnel has the required inbound gateway and tunnel ID.
    private boolean
    hasPeerOnCooldown(List<Hash> peers, long cooldownCutoff)
    Whether any non-self peer in the list is on cooldown.
    (package private) static boolean
    Emergency collapse is deferred while valid tunnels remain (soft-degraded or FAILING-but-not-FAILED).
    (package private) boolean
    Whether any active tunnel in this pool is a zero-hop fallback while the pool is configured for multi-hop.
    (package private) void
    Increment consecutive build timeout counter.
    boolean
    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 boolean
    Whether the tunnel's next hop is backlogged, meaning congested.
    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.
    private boolean
    isCachedLeaseSetValid(LeaseSet cached, long now)
    Whether the cached LeaseSet can be reused.
    private boolean
    isDeficitBuildNeeded(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 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.
    (package private) static boolean
    isDeficitThrottled(long now, long lastBuild, int healthySafe, int safeActive, int inProgress, boolean incompleteLeaseSet)
    Whether a deficit-build batch must wait.
    private boolean
    Check if the destination is reachable by looking up its LeaseSet.
    private boolean
    Reject re-adds of the same object (buildComplete called twice) and different objects with the same gateway tunnel ID.
    private boolean
    isEligibleDegradedCandidate(TunnelInfo t, boolean isServerPool, long now)
    Whether the tunnel may be replaced as a degraded candidate.
    private boolean
    isEligibleForLease(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 boolean
    Whether an emergency build ran within the cooldown window.
    (package private) static boolean
    isEmergencyCooldownActive(long elapsedMs, long usableCount, long inProgressCount)
    Whether the emergency-cooldown window has elapsed.
    private boolean
    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.
    (package private) static boolean
    isEnsureThrottled(long now, long lastEnsure, int usableTunnelCount, boolean incompleteLeaseSet)
    Whether ensureSufficientTunnels() must wait for this pool.
    (package private) 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.
    (package private) 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.
    (package private) 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.
    (package private) boolean
    isExpiringSoon(long now)
    Check if the LeaseSet is expiring soon (within 1 minute).
    private boolean
    Fresh builds only apply to live, non-exploratory inbound pools.
    (package private) 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.
    private boolean
    Whether this tunnel's local path is unusable because we hold no transport session to the peer we actually send through.
    private boolean
    isLongEnoughLived(TunnelInfo info, long now)
    Only keep tunnels that outlive the 60s add window.
    (package private) static boolean
    isMateriallyShort(int leases, int wanted)
    Whether a published LeaseSet is too small to count as a working set.
    private boolean
    Paired-pool gate: skip pruning entirely when this inbound pool's opposite outbound pool is below its target.
    private boolean
    Whether this is a short-lived ping pool.
    private boolean
    Skip while the pool's build executor is in backoff.
    (package private) 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.
    (package private) boolean
    Package-visible for the build executor: whether this pool's in-flight builds should bypass the global dispatch budget.
    private boolean
    isProtectedFromPruning(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 boolean
    isProtectedInUse(TunnelInfo t, long wallNow)
    Whether an UNTESTED tunnel expiring within the pre-build window is protected: it recently carried verified traffic.
    private boolean
    isPrunableNonGood(TunnelInfo t, boolean isServerPool)
    True for tunnels that should be pruned: FAILED/FAILING/unknown-status or failed in test.
    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.
    private boolean
    isRecentlyAddedDuplicate(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 boolean
    Whether the refresh throttle window is still active.
    private boolean
    Whether this is a server (inbound, non-exploratory) pool.
    (package private) static boolean
    Whether the tunnel is soft-degraded for ensure/deficit purposes: still in the pool and not FAILING, but soft failures have reached SOFT_DEGRADED_FOR_ENSURE.
    (package private) static boolean
    isSoftSendFailure(int status)
    Whether the status is a soft best-effort send timeout (3).
    boolean
    Whether this pool is struggling to meet its tunnel targets.
    (package private) static boolean
    isTrafficProvenUntestedLeaseCandidate(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 worst toPrune.
    private static long
    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 Hash
    The peer whose transport session this tunnel sends through, or null when there is none.
    private String
    The transport a tunnel's local hop uses, folded to one of the known names.
    protected LeaseSet
    Build a leaseSet with the required tunnels that aren't about to expire.
    private void
    Warn once per cycle when zombie or stale untested tunnels were removed.
    private void
    Logs a new tunnel's config and peers at debug level.
    private void
    Log the no-tunnels warning, distinguishing an unreachable destination (no LeaseSet found) from a pool that holds nothing usable.
    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.
    private void
    Warn when the peer selector found nothing for a new tunnel, naming the destination.
    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.
    private void
    markFailedTunnelsForEarlyExpiry(List<TunnelInfo> toRemove, int target, long now)
    Mark completely failed tunnels for early expiry (all pool types).
    private void
    markRotatedOut(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 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.
    private void
    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.
    (package private) boolean
    Do we really need more fallbacks? Used to prevent a zillion of them.
    (package private) static String
    noPeersSelectedLine(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) void
    Called by TestJob when a server pool tunnel fails a test but is retained.
    private void
    Offer tunnels that have never produced a latency reading.
    (package private) static int
    Rank a candidate test partner; lower is better.
    (package private) static boolean
    passesScanGates(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.
    private static List<Hash>
    The tunnel's peers, endpoint first, the order selectPeers() returns them in.
    private TunnelInfo
    Pick any live tunnel from the pool, expired ones excluded.
    private TunnelInfo
    Last-resort scan: accept any non-expired tunnel, even one over the consecutive-failure cap.
    (package private) void
    Proactively republish the LeaseSet when all tunnels are healthy (all > 5 min expiry).
    private void
    Process removal statistics for removed tunnels.
    private void
    pruneExcessGoodTunnels(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.
    int
    Prune excess tunnels from the pool to stay within budget.
    private void
    pruneForLeaseSurge(List<TunnelInfo> toRemove, int leaseSurge, long now)
    Schedule early expiry for the least lease-material GOOD tunnels while a lease surge is active.
    private void
    Remove non-GOOD tunnels from the pool when enough GOOD ones remain.
    private void
    After publishing a LeaseSet, prune GOOD tunnels that weren't included in the published LeaseSet.
    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.
    private void
    pruneOverReserve(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 int
    Prune priority rank — lower prunes first.
    private void
    Remove any zero-hop bootstrap tunnels from the pool.
    private boolean
    Does this pool publish a LeaseSet to the network? Structural: inbound + non-exploratory.
    private void
    Build and publish the first LeaseSet on startup for server pools so clients can find the destination immediately.
    private void
    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 void
    Build the current LeaseSet from the pool and publish it, pruning tunnels that were dropped from the LeaseSet.
    private LeaseSet
    Build a fresh LeaseSet from the surviving tunnels after a removal.
    private void
    recordBuildOutcome(boolean timedOut, long now)
    Record one build outcome in the fixed-width rate window.
    private void
    Record peer cooldown on build failure so bad OBEPs/IBGWs aren't retried.
    private void
    recordPeerCooldowns(List<Hash> peers, long now)
    Record a diversity cooldown for every reused peer so selectPeers respects them on the next selection.
    private void
    Record activity on a pooled tunnel (no-op for non-pooled).
    (package private) void
    No-op for outbound and exploratory pools.
    void
    refreshLeaseSet(boolean force)
    Refresh the LeaseSet, throttled to prevent flooding but not on initial creation.
    private void
    Re-reads the pool's settings from the config.
    private void
    Raise the peer-selection activity window to its ceiling because a pool has no usable tunnels, and publish the new value.
    private void
    Remove a tunnel config from the dispatcher and the expiration queue, in that order.
    (package private) void
    Remove a config that was never actually sent to the network (e.g.
    private void
    Remove a rotated-out tunnel from the pool.
    (package private) void
    Remove a tunnel from the pool.
    private void
    Remove a tunnel from the pool and, if it is a build config, from the dispatcher and the expiry map as well.
    private void
    Remove tunnels from the pool in one batch under the lock, then update stats outside the lock.
    (package private) boolean
    Synchronous tunnel removal for use during recovery or critical situations.
    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).
    private boolean
    Whether the tunnel replaces the current zero-hop candidate: it expires later.
    (package private) void
    reportSendFailure(TunnelInfo cfg, int status)
    Report a data-phase dispatch failure for a tunnel in this pool.
    private void
    reportZombies(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 void
    Republish the rebuilt LeaseSet, or fall back to a fresh build when no LeaseSet could be built.
    void
    Request new inbound tunnels so the pool holds the target count with at least getLeaseViabilityWindow(RouterContext) remaining.
    private void
    Request lease set from client for the primary and all aliases.
    private void
    requestLeaseSet(LeaseSet ls, boolean force)
    Request LeaseSet with optional force bypass of throttle.
    void
    requestLeaseSurge(int count)
    Note how many advertiseable tunnels the imminent mint needs, so pruneExcessTunnels() can offer up the ones that are no longer advertiseable until the pool has enough.
    private void
    Reset consecutive timeout counter when tunnels are working properly.
    private Boolean
    resolveRefreshForce(long now, boolean force)
    Resolve whether this refresh must bypass the throttle, or defer it.
    private void
    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 long
    rotateWindowStart(long windowStart, long now, long windowMs)
    Roll the build-rate window forward when it has expired.
    scanPoolForTunnel(int startIdx, long now, boolean longTunnelsOnly, TunnelInfo lastResortTunnel)
    Scan the pool from a random start index for a usable tunnel.
    private void
    Defer LeaseSet republish so rapid consecutive failures in server pools are batched into a single LeaseSet update.
    private void
    Schedule a deferred LeaseSet refresh to fire after the throttle window expires.
    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.
    private void
    Offer one last-chance test for each stale-but-testable UNTESTED tunnel the sweep kept.
    private void
    Schedule the collected peers for priority testing if the peer test job is available.
    private void
    Seed an UNTESTED paired tunnel as GOOD on build success so the pool has at least one usable tunnel.
    private TunnelInfo
    selectFromPool(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) TunnelInfo
    Pull a random tunnel out of the pool.
    private TunnelInfo
    selectTunnel(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) TunnelInfo
    selectTunnel(Hash closestTo)
    Return the tunnel from the pool that is XOR-closest to the target.
    (package private) TunnelInfo
    Select a tunnel to serve as a test round's partner, preferring partners that have demonstrated they work.
    private void
    Shorten the length when under extreme stress, else clear the override.
    (package private) void
    Paired pool (inbound <-> outbound for same destination).
    (package private) void
    Update the settings for this pool.
    private boolean
    Determine if we should perform detailed failure analysis Only analyze every Nth failure to avoid excessive processing.
    private boolean
    shouldBlamePaired(boolean success)
    Whether the paired tunnel should be blamed for this build result.
    (package private) 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.
    private boolean
    Dedup: skip if a deficit build ran within the last 5 seconds.
    (package private) 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.
    (package private) 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.
    private boolean
    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 boolean
    shouldDropForLostSession(long firstMissingAt, long now, long hysteresis)
    Whether a tunnel whose local session is gone should be dropped from the pool.
    private boolean
    Suppress last-resort tunnel warning spam with rate limiting.
    private boolean
    Suppress "no tunnels available" warning spam with rate limiting Uses adaptive suppression between 5 and 10 minutes based on failures.
    (package private) static boolean
    shouldLogSendFailure(long now, long lastLogTime)
    Whether a data-phase failure or condemnation line should be logged now.
    private boolean
    Whether this pool may report a materially short LeaseSet again.
    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).
    (package private) 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.
    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.
    private boolean
    shouldSkipDueToDedup(int inProgress, int safeActive, long now)
    Dedup guard: skip the cycle when concurrent events (prune, expire, removal) all trigger within 5s.
    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.
    private boolean
    shouldSkipDueToInProgress(int safeActive, int target, int inProgress, double timeoutRate)
    Variant taking the build timeout rate, which the hot path already has.
    private boolean
    IB/OB balance: don't emergency-build if this direction already has MORE usable tunnels than its paired direction.
    (package private) static boolean
    shouldWarnSelectPeersFailure(long uptime, long now, long lastWarnTime)
    The selectPeers-failure WARN is rate-limited: never before SELECT_PEERS_FAILURE_SILENCE_MS of uptime, then at most once per SELECT_PEERS_FAILURE_WARN_INTERVAL_MS per pool.
    (package private) void
    Shut down the pool and clean up resources.
    int
    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 int
    The soft failure count that marks a tunnel for retention.
    (package private) static long
    stampLostSession(long currentMissingAt, long now)
    The value to record for a tunnel whose local transport session has just been observed missing.
    private void
    Record that this tunnel's local transport session was seen missing, keeping the first observation as the start of the duration.
    (package private) void
    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 void
    swapInReplacement(TunnelInfo replacee, TunnelInfo info, TunnelId gatewayId, long now)
    Swap the replacement into the pool and record it as recently added.
    sweepExpiredAndCountTunnels(long now, long preBuildThreshold)
    Drops expired tunnels and counts the rest.
    private void
    Blame all peers in tunnel, with a probability inversely related to tunnel length Enhanced with intelligent blame distribution and recovery consideration.
    (package private) static double
    timeoutRate(long attempts, long timeouts)
    Fraction of counted build attempts that timed out.
    Description of the pool and its settings.
    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.
    (package private) void
    Remove tunnel and blame all peers (not necessarily equally).
    (package private) void
    tunnelFailed(TunnelInfo cfg, Hash blamePeer)
    Remove the tunnel and blame only one peer.
    (package private) void
    Mark a tunnel failed without blaming any peer.
    private String
    Identify a tunnel by its owning destination's base32 prefix.
    (package private) void
    updateAdaptiveTestDuration(long durationMs)
    Record a completed test duration and adapt this pool's test window.
    (package private) static double
    updatedAdaptiveTestMultiplier(double mult, long avgMs)
    Next test timeout multiplier for a given rolling average.
    (package private) static long
    updatedAvgTestDuration(long avg, long sample)
    Fold a completed test duration into the rolling average.
    private void
    Update the paired tunnel profiles by treating the build as a tunnel test.
    private void
    Recomputes the pool's bandwidth and lifetime rates.
    (package private) static int
    usableLeaseMinimum(int wanted)
    The smallest LeaseSet treated as usable for a given target.
    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.
    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).
    (package private) static String
    Normalise a RouterInfo transport style into one of the known transports.

    Methods inherited from class Object

    clone, equals, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Field Details

    • EXPIRATION_COMPARATOR

      private static final Comparator<TunnelInfo> EXPIRATION_COMPARATOR
      Sorts tunnel infos by expiration, soonest first.
    • QUALITY_COMPARATOR

      static final Comparator<TunnelInfo> 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

      private final List<PooledTunnelCreatorConfig> _inProgress
      Configs with builds in flight, guarded by the manager.
    • _context

      protected final RouterContext _context
      The router context.
    • _log

      protected final Log _log
      The log.
    • _settings

      private TunnelPoolSettings _settings
      The pool's current settings.
    • _tunnels

      private final List<TunnelInfo> _tunnels
      The pool's live tunnels.
    • _tunnelsLock

      private final ReentrantLock _tunnelsLock
      Lock guarding the tunnel list.
    • _peerSelector

      private final TunnelPeerSelector _peerSelector
      Selector choosing peers for this pool's tunnels.
    • _manager

      private final TunnelPoolManager _manager
      The manager that owns this pool.
    • _pairedPool

      private TunnelPool _pairedPool
      The opposite-direction pool for the same destination.
    • _alive

      protected volatile boolean _alive
      Whether this pool is running.
    • _lifetimeProcessed

      private long _lifetimeProcessed
      Total lifetime of tunnels this pool has used.
    • _started

      private long _started
      When the pool was started.
    • _lastRateUpdate

      private long _lastRateUpdate
      When the pool's rates were last recomputed.
    • _lastLifetimeProcessed

      private long _lastLifetimeProcessed
      Lifetime total at the last rate update.
    • _rateName

      private final String _rateName
      Stat name prefix for this pool's rates.
    • _firstInstalled

      private final long _firstInstalled
      When the pool was first installed.
    • _consecutiveBuildTimeouts

      private final AtomicInteger _consecutiveBuildTimeouts
      Build timeouts in a row, for stress detection.
    • BUILD_RATE_WINDOW_MS

      private static final long BUILD_RATE_WINDOW_MS
      Fixed (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, see timeoutRate(long, long) and computePreBuildWindowMs(double).
      See Also:
    • SHORTFALL_LOG_INTERVAL_MS

      private static final long SHORTFALL_LOG_INTERVAL_MS
      Minimum gap between repeated "not enough leases" reports from one pool.
      See Also:
    • ZOMBIE_STAT_HEALTHY

      static final String ZOMBIE_STAT_HEALTHY
      Rate stat recording how many tunnels getHealthyTunnelCount() dropped for want of a transport session to their local hop.
      See Also:
    • ZOMBIE_STAT_USABLE

      static final String ZOMBIE_STAT_USABLE
      Rate stat recording how many tunnels getUsableTunnelCount() dropped for the same reason. Kept separate from ZOMBIE_STAT_HEALTHY because 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_MS
      Minimum 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 _nextZombieLog
      Wall-clock guard for the zombie warning; see ZOMBIE_LOG_INTERVAL_MS.
    • _windowAttempts

      private final AtomicLong _windowAttempts
      Build attempts in the current stats window.
    • _windowTimeouts

      private final AtomicLong _windowTimeouts
      Build timeouts in the current stats window.
    • _windowStartMs

      private volatile long _windowStartMs
      When the current stats window began.
    • _rateWindowLock

      private final Object _rateWindowLock
      Guards the window rotation; one lock per build completion.
    • PRE_BUILD_WINDOW_MS

      static final long PRE_BUILD_WINDOW_MS
      Base 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_MS
      Cap 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_MS
      Minimum 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_SWEEP
      Cap 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

      private final RateStat[] _expireStatSlot
      Cached rate-stat handles so per-build lookups skip the StatManager map.
    • _rejectStatSlot

      private final RateStat[] _rejectStatSlot
      Holds the rejected-build stat once registered.
    • _successStatSlot

      private final RateStat[] _successStatSlot
      Holds the successful-build stat once registered.
    • _buildSuccessRateStatSlot

      private final RateStat[] _buildSuccessRateStatSlot
      Holds the build success rate stat once registered.
    • _avgBWStatSlot

      private final RateStat[] _avgBWStatSlot
      Holds the average bandwidth stat once registered.
    • _lastNoTunnelsWarningTime

      private long _lastNoTunnelsWarningTime
      When the last no-tunnels warning was logged.
    • _lastLastResortLogTime

      private long _lastLastResortLogTime
      When the last last-resort tunnel log was written.
    • _lastSelectPeersFailureWarnTime

      private long _lastSelectPeersFailureWarnTime
      When the last selectPeers failure warning was logged.
    • _lastSendFailureLogTime

      private long _lastSendFailureLogTime
      Rate-limit stamps for the data-phase failure / condemnation log lines.
    • _lastCondemnWarnTime

      private long _lastCondemnWarnTime
      When the last condemn warning was logged.
    • _consecutiveEmergencies

      private volatile int _consecutiveEmergencies
      Dynamic 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_BOOST
      Most tunnels an emergency pass may add.
      See Also:
    • MAX_BUILD_PER_POOL_DIR

      static final int MAX_BUILD_PER_POOL_DIR
      Hard 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_THRESHOLD
      Higher 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_FRACTION
      Pre-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_BURST
      Builds started in one pre-emergency burst. Named so it stays consistent with PRE_EMERGENCY_FRACTION rather than sitting as a bare literal in the trigger.
      See Also:
    • MAX_INPROGRESS_PER_POOL_DIR

      static final int MAX_INPROGRESS_PER_POOL_DIR
      Absolute 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 _adaptiveAvgTestDuration
      Rolling average test duration for this pool, ms; 0 until the first completion is recorded. Held here rather than on TestJob because 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 _adaptiveTestMultiplier
      Test timeout multiplier for this pool, 1.0 until adapted.
    • SLOW_TEST_AVG_MS

      static final long SLOW_TEST_AVG_MS
      Rolling average test duration above which the test window is widened.
      See Also:
    • FAST_TEST_AVG_MS

      static final long FAST_TEST_AVG_MS
      Rolling average test duration below which the test window is narrowed.
      See Also:
    • MAX_ADAPTIVE_TEST_MULTIPLIER

      static final double MAX_ADAPTIVE_TEST_MULTIPLIER
      Hard 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_ENSURE
      Soft-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_PRIORITY
      Priority 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 _lastFallbackWarnTime
      Last time buildFallback() logged its zero-hop-refusal warning, to rate-limit it.
    • STRUGGLE_RESERVE

      private static final int STRUGGLE_RESERVE
      Extra 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_THRESHOLD
      Consecutive build timeouts at which the pool starts holding one spare tunnel above target (see computeDifficultySpare(int)).
      See Also:
    • SPARE_HIGH_THRESHOLD

      static final int SPARE_HIGH_THRESHOLD
      Consecutive build timeouts at which the spare grows to two tunnels.
      See Also:
    • IN_USE_TRAFFIC_MS

      private static final long IN_USE_TRAFFIC_MS
      A 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 _lastPreBuildTime
      Minimum 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 _everHadHealthyTunnel
      True 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 in ensureSufficientTunnels().
    • _lastThinPoolWarned

      private volatile int _lastThinPoolWarned
      Last 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_MS
      Pre-build passes are at least 1 minute apart.
      See Also:
    • _lastEmergencyBuildTime

      private volatile long _lastEmergencyBuildTime
      When the last emergency build pass ran.
    • EMERGENCY_COOLDOWN_MS

      private static final long EMERGENCY_COOLDOWN_MS
      An emergency build pass is followed by a 30 second wait.
      See Also:
    • EMERGENCY_COLLAPSE_COOLDOWN_MS

      private static final long EMERGENCY_COLLAPSE_COOLDOWN_MS
      Shorter 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 _leaseSetRepublishPending
      Set when a lease set republish is queued.
    • _ensuringTunnels

      private final AtomicBoolean _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 _lastDeficitBuildTime
      Last 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 _leaseSurgeNeed
      Lease-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 -- pruneExcessTunnels only 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 by countFreshTunnels. 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. RepublishLeaseSetJob raises 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 and getActiveTarget() is at least 2, so folding it into getEffectiveTarget() 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_MS permanently, 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_MS
      Normal 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_MS
      Floor 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_MS
      Intermediate 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 _lastRemovalTime
      Last 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 short ENSURE_COLLAPSED_MIN_MS floor instead of waiting out the healthy 15s gate.
    • DEFICIT_THROTTLE_MS

      private static final long DEFICIT_THROTTLE_MS
      Minimum interval between deficit-build batches on a non-collapsed pool.
      See Also:
    • DEFICIT_COLLAPSE_COOLDOWN_MS

      private static final long DEFICIT_COLLAPSE_COOLDOWN_MS
      Shorter 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. Matches EMERGENCY_COLLAPSE_COOLDOWN_MS so recovery stays prompt without build-storming; concurrent builds stay capped by shouldSkipDueToInProgress(int, int, int).
      See Also:
    • DEFICIT_DEGRADED_MS

      private static final long DEFICIT_DEGRADED_MS
      Intermediate 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 _lastEnsureTime
      Throttle for ensureSufficientTunnels hot path (per pool). See ENSURE_THROTTLE_MS and ENSURE_COLLAPSED_MIN_MS.
    • _preEmergencyBurstAt

      private volatile long _preEmergencyBurstAt
      Last 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_DIR cap 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_MS
      How 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_EXPIRY
      Default early expiration time for pruned tunnels (120 seconds).
      See Also:
    • PROP_PRUNE_EARLY_EXPIRY

      private static final String PROP_PRUNE_EARLY_EXPIRY
      Config key for the early tunnel expiry margin.
      See Also:
    • LEGACY_PROP_PRUNE_EARLY_EXPIRY

      private static final String LEGACY_PROP_PRUNE_EARLY_EXPIRY
      Legacy alias for PROP_PRUNE_EARLY_EXPIRY, read only as a fallback.
      See Also:
    • PRUNE_KEEP_IF_FRESH_MS

      private static final long PRUNE_KEEP_IF_FRESH_MS
      Non-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_MS
      Non-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_1
      If 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_2
      Build tries override for the second consecutive failure.
      See Also:
    • BUILD_TRIES_LENGTH_OVERRIDE_CLIENT_1

      private static final int BUILD_TRIES_LENGTH_OVERRIDE_CLIENT_1
      Build 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_2
      Build tries override for a client pool's second failure.
      See Also:
    • LEASE_SAFETY_MARGIN

      private static final long LEASE_SAFETY_MARGIN
      Lease 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_MS
      Hard ceiling on getTunnelLifetime(RouterContext): the standard 10m lifetime plus the 1m grace period other routers afford. The published lease is capped separately at getLeaseMaxDuration(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_MS
      Tunnels must have at least this much remaining life to be published.

      The lease ends LEASE_SAFETY_MARGIN before 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 the leaseMaxDuration cap 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_MS
      How 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_MS
      How 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 _lastSessionLossProbe
      Last 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_MS
      Interval between lost-session attribution samples.
      See Also:
    • RECENTLY_ADDED_WINDOW

      private static final long RECENTLY_ADDED_WINDOW
      Track recently-added tunnel IDs to prevent duplicates. Uses a simple sliding window based on add time. Window is 60 seconds — long enough to catch duplicate addTunnel(TunnelInfo) calls for the same tunnel.
      See Also:
    • _lastRefreshTime

      private long _lastRefreshTime
      Throttle 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 _lastLeaseSetPublishTime
      Track last proactive LeaseSet publish time for rate limiting.
    • _cachedLeaseSet

      private volatile LeaseSet _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 _lastLeaseSetBuildTime
      Timestamp of the last successful LeaseSet build.
    • _hasGoodLeaseSet

      private boolean _hasGoodLeaseSet
      True after first LeaseSet with at least one lease was built (not necessarily GOOD).
    • _hasIncompleteLeaseSet

      private boolean _hasIncompleteLeaseSet
      True when last built LeaseSet had fewer leases than wanted — bypass cache.
    • _lastShortfallLog

      private volatile long _lastShortfallLog
      When this pool last reported a materially short LeaseSet
    • _lastPublishedEarliestLeaseEnd

      private long _lastPublishedEarliestLeaseEnd
      Earliest 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

      private final AtomicBoolean _pendingRefreshScheduled
      Track if a deferred refresh is already scheduled.
    • _recentlyAddedTunnels

      private final Map<TunnelId,Long> _recentlyAddedTunnels
      Tunnels added recently, by id, for duplicate suppression.
    • _nearExpiryCount

      private volatile int _nearExpiryCount
      Live tunnels whose lease is inside the pre-build window, published by the last sweep so BuildExecutor'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_MS
      Minimum gap between data-phase failure log lines, per pool.
      See Also:
    • SELECT_PEERS_FAILURE_SILENCE_MS

      private static final long SELECT_PEERS_FAILURE_SILENCE_MS
      Silent 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_MS
      At most one selectPeers-failure WARN per interval per pool.
      See Also:
  • Constructor Details

    • TunnelPool

      Tunnel pool.
      Parameters:
      ctx - the router context
      mgr - the pool manager owning this pool
      settings - the pool's tunnel quantity, direction and exploratory flags
      sel - the peer selector used to choose tunnel build participants
  • Method Details

    • compareAvgLatency

      private static int compareAvgLatency(TunnelInfo a, TunnelInfo b)
      Latency ascending, tunnels with no measurements sort last.
      Parameters:
      a - the tunnel to order first
      b - 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 none
      sample - 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_MULTIPLIER so a persistently slow path cannot widen the window without bound and mask genuinely dead tunnels.

      Parameters:
      mult - current multiplier, 1.0 up to MAX_ADAPTIVE_TEST_MULTIPLIER
      avgMs - 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 never
      now - current time in ms
      window - 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 from router.pruneEarlyExpiryDelay, falling back to the legacy router.tunnel.pruneEarlyExpiryDelay alias, then the default (120s).
      Returns:
      early expiry time in milliseconds
    • getPruneEarlyExpiry

      static long getPruneEarlyExpiry(RouterContext ctx)
      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: canonical PROP_PRUNE_EARLY_EXPIRY first, then the legacy LEGACY_PROP_PRUNE_EARLY_EXPIRY alias kept for configs and scripts that predate the canonical name, else DEFAULT_PRUNE_EARLY_EXPIRY.
      Parameters:
      ctx - the router context
      Returns:
      early expiry time in milliseconds
    • getTunnelLifetime

      public static int getTunnelLifetime(RouterContext ctx)
      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 is min(tunnel expiry, now + leaseMaxDuration) - 60s, so with an 11m tunnel and the 10m getLeaseMaxDuration(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

      public static long getLeaseViabilityWindow(RouterContext ctx)
      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

      static int getMaxConcurrentBuildsPerDirection(RouterContext ctx)
      Max concurrent builds per direction from config or default (6). Tunable via i2p.tunnel.maxConcurrentBuildsPerDirection (default: 6).
      Parameters:
      ctx - the router context
      Returns:
      the max concurrent builds per direction
    • getStartupTime

      static long getStartupTime(RouterContext ctx)
      Startup suppression period from config or default (5 minutes). Tunable via i2p.tunnel.startupTime (default: 300000).
      Parameters:
      ctx - the router context
      Returns:
      the startup time in milliseconds
    • getBuildTriesQuantityOverride

      static int getBuildTriesQuantityOverride(RouterContext ctx)
      Quantity override threshold from config or default (12). If less than one success in this many, reduce quantity. Tunable via i2p.tunnel.buildTriesQuantityOverride (default: 12).
      Parameters:
      ctx - the router context
      Returns:
      the quantity override threshold
    • getRefreshThrottle

      static long getRefreshThrottle(RouterContext ctx)
      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 via i2p.tunnel.refreshThrottle (default: 120000).
      Parameters:
      ctx - the router context
      Returns:
      the refresh throttle interval in milliseconds
    • getLeaseSetBuildMinInterval

      static long getLeaseSetBuildMinInterval(RouterContext ctx)
      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 via i2p.tunnel.leasesetBuildMinInterval (default: 120000).
      Parameters:
      ctx - the router context
      Returns:
      the LeaseSet build minimum interval in milliseconds
    • getLeaseMaxDuration

      static long getLeaseMaxDuration(RouterContext ctx)
      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

      private String 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

      private void createRateStatForName(String name)
      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

      public String getRateName()
      RateStat name for the bandwidth graph.
      Returns:
      non-null
    • getTunnelPoolManager

      public TunnelPoolManager getTunnelPoolManager()
      TunnelPoolManager that owns this pool.
      Returns:
      non-null
    • getRateStat

      private RateStat getRateStat(RateStat[] slot, String name)
      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 found
      name - 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

      private TunnelInfo selectTunnel(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.
      Parameters:
      allowRecurseOnFail - true to retry once after building a fallback
      Returns:
      null on failure, but it should always build and return a fallback
    • selectFromPool

      private TunnelInfo selectFromPool(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. Logs the no-tunnels warning (rate-limited) when the pool is empty.
      Parameters:
      now - current time in ms
      uptime - router uptime in ms
      avoidZeroHop - true to skip zero-hop tunnels on the first pass
      Returns:
      a usable tunnel, or null
    • handleBacklogged

      private TunnelInfo handleBacklogged(TunnelPool.ScanResult pass)
      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 null
      backlogged - the tunnel whose local hop is backlogged, or null
      uptime - 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:

      1. 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
      2. tested GOOD
      3. carrying real traffic, status not yet known
      4. 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

      static int partnerPreferenceTier(TunnelInfo info)
      Rank a candidate test partner; lower is better. See selectTunnelForTest() 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 pass
      now - current time in ms, for the expiry check
      longTunnelsOnly - first-pass mode (zero-hop-avoiding pools): long tunnels only
      lastResortTunnel - first-pass result, kept by the second pass if set
      Returns:
      the scan result
    • passesScanGates

      static boolean passesScanGates(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. Longer than a single hop. Pure decision. No side effects.
      Parameters:
      info - the candidate tunnel
      now - current time in ms
      longTunnelsOnly - first-pass mode (zero-hop-avoiding pools): long tunnels only
      Returns:
      whether the tunnel is a scan candidate
    • pickDegradedTunnel

      private TunnelInfo pickDegradedTunnel(long now)
      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 reporting Usable: 0 against 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 locked
      now - current time in ms
      backlogged - tests whether a tunnel's next peer is backlogged
      Returns:
      the classified counts
    • backlogTest

      private java.util.function.Predicate<TunnelInfo> backlogTest()
      Backlog predicate matching the one scanPoolForTunnel(int, long, boolean, TunnelInfo) applies.
      Returns:
      the predicate, or null for an inbound pool, whose tunnels have no next peer to be backlogged
    • logNoTunnelsWarning

      private void logNoTunnelsWarning(TunnelPool.PoolState state)
      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

      private void recordPooledActivity(TunnelInfo info)
      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

      TunnelInfo selectTunnel(Hash closestTo)
      Return the tunnel from the pool that is XOR-closest to the target. By using this instead of the random selectTunnel(), 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

      private TunnelInfo findClosestTunnel(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. 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 to
      avoidZeroHop - true to leave single-hop tunnels out of the running
      now - current time in ms, compared against each tunnel's expiration
      Returns:
      the closest tunnel, or null
    • pickAnyNonExpiredTunnel

      private TunnelInfo pickAnyNonExpiredTunnel(long now)
      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

      public TunnelInfo getTunnel(TunnelId gatewayId)
      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

      public List<TunnelInfo> 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 length
      minLen - the floor for the shortened length
      th1 - build tries needed in the window before the override is considered, and the 1-in-th1 success ratio that shortens the length by one hop
      th2 - the 1-in-th2 success ratio that shortens it by two hops
      e - expire rate stat for this pool type, or null
      r - reject rate stat for this pool type, or null
      s - 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 length
      minLen - the floor for the shortened length
      th1 - the 1-in-th1 success ratio that shortens by one hop
      th2 - the 1-in-th2 success ratio that shortens by two hops
      tot - build tries in the window: expire plus reject plus success
      sc - successful builds out of tot
      Returns:
      true if the override was applied
    • listPending

      public List<PooledTunnelCreatorConfig> 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 as getUsableTunnelCount() plus a floor on TunnelInfo.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

      private void reportZombies(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.

      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 of ZOMBIE_STAT_HEALTHY or ZOMBIE_STAT_USABLE
      kept - the number of tunnels this count is returning to its caller
      zombies - the tunnels dropped for want of a local transport session, or null when there were none
    • localHop

      private Hash localHop(TunnelInfo t)
      The peer whose transport session this tunnel sends through, or null when there is none. Direction-dependent, mirroring BuildRequestor.getBuildRequestPeer.
      Parameters:
      t - the pool tunnel; may be null or zero-length
      Returns:
      the local hop, or null
    • countSessionLossCause

      private void countSessionLossCause(Hash hop)
      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

      private boolean isLocalHopUnreachable(TunnelInfo t)
      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 TunnelInfo and its lease survive untouched: nothing marks the tunnel failed, and getHealthyTunnelCount() -- 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 tunnel getPeer(0) is the peer we send to, but for outbound getPeer(0) is the gateway we want to reach and getPeer(1) is our next hop. Checking getPeer(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 at addTunnel(TunnelInfo), when the session must have existed for the build to complete. Reading isEstablished on 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 built
      establishedNow - true when a session to that hop exists now
      peerReachable - 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 recorded
      now - current time in ms
      hysteresis - 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 notice
      now - current time in ms
      Returns:
      the timestamp to store
    • stampLostSession

      private void stampLostSession(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.
      Parameters:
      t - the tunnel; must not be null
      now - current time in ms
    • clearLostSession

      private void clearLostSession(TunnelInfo t)
      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

      static String zombieTransportBucket(String style)
      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

      private String countZombieTransport(TunnelInfo t)
      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

      private String localHopTransportBucket(TunnelInfo t)
      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

      private void countLocalHopTransports(List<TunnelInfo> tunnels)
      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

      private boolean isBackloggedNextHop(TunnelInfo t)
      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

      static boolean isSoftDegraded(TunnelInfo t)
      Whether the tunnel is soft-degraded for ensure/deficit purposes: still in the pool and not FAILING, but soft failures have reached SOFT_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, so pruneExcessTunnels() 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

      public TunnelPoolSettings getSettings()
      Pool settings.
      Returns:
      the settings for this pool
    • setSettings

      void setSettings(TunnelPoolSettings settings)
      Update the settings for this pool.
      Parameters:
      settings - the new settings, may be null
    • setPairedPool

      void setPairedPool(TunnelPool pool)
      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

      void removeInProgress(PooledTunnelCreatorConfig cfg)
      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

      public List<PooledTunnelCreatorConfig> cancelExcessInProgress(int maxAllowed)
      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. Uses ExpireJob.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 to
      isServerPool - true for a pool that publishes a LeaseSet
      now - current time in ms
      currentSize - tunnels currently in the pool
      pruneThreshold - 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 pool
      isServerPool - 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 to
      isServerPool - true for a pool that publishes a LeaseSet, which never prunes GOOD or FAILING tunnels
      goodTarget - how many GOOD tunnels a non-server pool must keep
      now - current time in ms
      toPrune - how many tunnels still need pruning
      Returns:
      the remaining deficit not yet pruned
    • isProtectedFromPruning

      private boolean isProtectedFromPruning(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. 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 pruning
      isServerPool - true for a pool that publishes a LeaseSet
      now - 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 early
      now - current time in ms, the base of the new expiration
      staggerMs - additional delay to stagger removals
      overBudget - mark over budget (true) or too slow (false)
    • pruneExcessGoodTunnels

      private void pruneExcessGoodTunnels(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.
      Parameters:
      toRemove - list the early-expired tunnels are added to
      toPrune - how many tunnels still need pruning
      now - current time in ms
    • pruneForLeaseSurge

      private void pruneForLeaseSurge(List<TunnelInfo> toRemove, int leaseSurge, long now)
      Schedule early expiry for the least lease-material GOOD tunnels while a lease surge is active.

      Counts tunnels with at least LEASE_MIN_REMAINING_MS of life left -- the same bar selectLeaseTunnels applies -- and prunes the soonest-expiring offenders until that many advertiseable tunnels remain, or there is nothing left to give. Tunnels that are isRecentlyActive() 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 null
      leaseSurge - how many advertiseable tunnels are needed
      now - 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 tunnels
      toRemove - already-scheduled prunes, excluded
      now - current time in ms
      Returns:
      prunable tunnels, in pool order
    • countLeaseViableTunnels

      static int countLeaseViableTunnels(List<TunnelInfo> tunnels, List<TunnelInfo> toRemove, long now)
      How many tunnels could still be advertised right now, on the same bar selectLeaseTunnels applies, matching the wording of the sibling prune path.
      Parameters:
      tunnels - the pool's tunnels
      toRemove - already-scheduled prunes, excluded
      now - current time in ms
      Returns:
      number of tunnels with lease admission remaining
    • markFailedTunnelsForEarlyExpiry

      private void markFailedTunnelsForEarlyExpiry(List<TunnelInfo> toRemove, int target, long now)
      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 to
      target - the tunnel count the pool should be left with
      now - current time in ms
    • pruneRank

      private static int pruneRank(TunnelTestStatus s)
      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 by ClientPeerSelector to relax the cross-tunnel peer diversity constraint when the pool is starved for build candidates.
      Returns:
      true if the pool is struggling
    • addTunnel

      protected void addTunnel(TunnelInfo info)
      Add a tunnel to the pool.
      Parameters:
      info - the tunnel to add
    • getGatewayTunnelId

      private TunnelId getGatewayTunnelId(TunnelInfo info)
      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

      private boolean isRecentlyAddedDuplicate(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.
      Parameters:
      info - the tunnel about to be added, named in the duplicate warning
      gatewayId - the gateway tunnel ID to look up
      now - current time in ms, used to age out stale entries
      Returns:
      true if a duplicate addition was detected (and logged)
    • isLongEnoughLived

      private boolean isLongEnoughLived(TunnelInfo info, long now)
      Only keep tunnels that outlive the 60s add window.
      Parameters:
      info - the tunnel about to be added
      now - 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 built
      gatewayId - its gateway tunnel ID, recorded as recently added
      now - current time in ms
      usable - usable tunnels in the pool before this add
      maxUsable - capacity ceiling above which the add is refused
      target - the ordinary active-count target, named in the at-capacity log
      Returns:
      false if the tunnel was dropped (pool full of GOOD tunnels)
    • isDuplicateInPool

      private boolean isDuplicateInPool(TunnelInfo info, TunnelId gatewayId)
      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 added
      gatewayId - 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 in
      gatewayId - its gateway tunnel ID, recorded as recently added
      now - current time in ms
      usable - usable tunnels in the pool before the swap
      maxUsable - capacity ceiling that put the pool at capacity
      target - the ordinary active-count target, named in the replacement log
      Returns:
      true if a tunnel was replaced, false if all tunnels are GOOD
    • findReplacee

      private TunnelInfo 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

      private void swapInReplacement(TunnelInfo replacee, TunnelInfo info, TunnelId gatewayId, long now)
      Swap the replacement into the pool and record it as recently added. Caller must hold _tunnelsLock.
      Parameters:
      replacee - the tunnel being swapped out
      info - the tunnel taking its slot
      gatewayId - info's gateway tunnel ID, recorded as recently added
      now - current time in ms
    • publishLeaseSetIfNeeded

      private void publishLeaseSetIfNeeded(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. 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 published
      ls - the freshly built LeaseSet, or null if none was built
      now - 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

      void removeTunnel(TunnelInfo info)
      Remove a tunnel from the pool.
      Parameters:
      info - the tunnel to remove
    • removeTunnelSynchronous

      boolean removeTunnelSynchronous(TunnelInfo info)
      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

      private LeaseSet 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

      private void republishAfterSynchronousRemoval(LeaseSet ls, int remaining)
      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 built
      remaining - 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

      private void removeFromDispatcherAndExpiration(TunnelInfo info)
      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 concurrently
      isServerPool - whether this is an inbound non-exploratory pool
      rotateForPropagation - 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 LeaseSet
      rotatedOut - 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

      private boolean hasGoodTunnel(List<TunnelInfo> tunnels, long expireAfter)
      Whether any tunnel in the list is GOOD and expiring after the propagation deadline — gates the UNTESTED fallback.
      Parameters:
      tunnels - the candidate tunnels
      expireAfter - 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 tunnels
      expireAfter - the earliest expiration a tunnel may have to be advertised
      hasGoodTunnel - whether the pool holds a GOOD tunnel, which decides whether UNTESTED tunnels qualify
      Returns:
      the tunnels to build the LeaseSet from, never null
    • isEligibleForLease

      private boolean isEligibleForLease(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.
      Parameters:
      tunnel - the tunnel to test
      hasGoodTunnel - whether the pool holds a GOOD tunnel, which decides whether an UNTESTED tunnel qualifies
      expireAfter - the earliest expiration the tunnel may have to qualify
      Returns:
      true if the tunnel may be included in the LeaseSet
    • collectTrafficProvenUntested

      private List<TunnelInfo> collectTrafficProvenUntested(List<TunnelInfo> tunnels, long expireAfter)
      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 inspect
      expireAfter - 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

      static boolean isTrafficProvenUntestedLeaseCandidate(TunnelInfo tunnel, long expireAfter, long now)
      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 (within TestJob.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 lapses
      now - wall-clock timestamp for the freshness check
      Returns:
      true if the tunnel is eligible to publish as a traffic-proven lease
    • replaceZeroHopCandidate

      private boolean replaceZeroHopCandidate(TunnelInfo zeroHopTunnel, TunnelInfo tunnel)
      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 is
      tunnel - the candidate offered in its place
      Returns:
      true if the offered tunnel should replace the current one
    • hasInboundGateway

      private boolean hasInboundGateway(TunnelInfo tunnel)
      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

      private static long leaseKeyOf(TunnelInfo tunnel)
      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

      TreeSet<Lease> 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.

      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 first
      zeroHopTunnel - 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 place
      tunnels - the pool's current tunnels, searched for a degraded one
      isServerPool - 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 out
      tunnels - the pool's current tunnels, searched for the rotated lease's tunnel
      rotatedOut - 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

      private void markRotatedOut(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.
      Parameters:
      rotated - the lease that was dropped from the set
      tunnels - the pool's current tunnels, searched by tunnel id and gateway
      rotatedOut - 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 assembled
      wanted - 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

      private LeaseSet finalizeLeaseSet(TreeSet<Lease> leases, int wanted, long now)
      Builds the lease set from the chosen leases.
      Parameters:
      leases - the leases to publish
      wanted - the number of leases wanted
      now - the current time
      Returns:
      the lease set, or null if it could not be built
    • processRemovalStats

      private void processRemovalStats(List<TunnelInfo> removed)
      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

      void tunnelFailed(TunnelInfo cfg)
      Remove tunnel and blame all peers (not necessarily equally). This may be called multiple times from TestJob. Enhanced with intelligent failure analysis and recovery.
      Parameters:
      cfg - the tunnel that failed
    • tunnelFailedWithoutBlame

      void tunnelFailedWithoutBlame(TunnelInfo cfg)
      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

      void tunnelFailed(TunnelInfo cfg, Hash blamePeer)
      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 failed
      blamePeer - the peer to blame
    • blameSinglePeer

      private void blameSinglePeer(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.
      Parameters:
      cfg - the tunnel that failed
      blamePeer - the single peer to blame, may be null
    • reportSendFailure

      void reportSendFailure(TunnelInfo cfg, int status)
      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 exceeds TunnelCreatorConfig.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 above SOFT_REMOVAL_THRESHOLD. Either way the tunnel is retained in the pool rather than dropped — see failWithCount(TunnelInfo, int).
      Parameters:
      cfg - the tunnel that carried the failed message
      status - the I2CP MessageStatusMessage failure code
    • describeHops

      private static String describeHops(TunnelInfo cfg)
      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

      private String 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 timeout
      failures - the failure count just recorded
      overBar - 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 than TunnelCreatorConfig.MAX_CONSECUTIVE_TEST_FAILURES; soft send timeouts remove only above SOFT_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 count
      soft - 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's i2cp.dontPublishLeaseSet option 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

      private void fail(TunnelInfo cfg)
      Marks a tunnel as failed and schedules its replacement.
      Parameters:
      cfg - the failed tunnel
    • alreadyCondemned

      static boolean alreadyCondemned(boolean isDead, TunnelTestStatus status)
      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 tripped
      status - the tunnel's current test status, may be null
      Returns:
      true when this report adds nothing
    • failWithCount

      private void failWithCount(TunnelInfo cfg, int failures)
      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, while addTunnel(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 failed
      failures - the count that exceeded the removal bar
    • forceTunnelFailure

      public void forceTunnelFailure(TunnelInfo cfg)
      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

      private void tellProfileFailed(TunnelInfo cfg)
      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

      private int computeBlamePct(TunnelInfo cfg, int i, int len, int start, int consecutiveFailures)
      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 blamed
      i - the hop index of the peer to blame, 0-based
      len - the tunnel's hop count including this router, at least 2
      start - the first blamable hop index: 1 for an outbound tunnel, which skips our own gateway, 0 for an inbound one
      consecutiveFailures - 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 tunnel
      pct - the blame percentage from computeBlamePct
      uptime - the router's uptime in ms
      startupTime - the router's startup time in ms, ms since the epoch
      consecutiveFailures - 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 collect
      peer - the peer to queue, skipped when null
      peersInTunnel - the number of blamable peers in the tunnel, used only to size the list
      startupTime - 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

      private void schedulePriorityPeerTests(List<Hash> peersToTest)
      Schedule the collected peers for priority testing if the peer test job is available.
      Parameters:
      peersToTest - the peers to schedule, skipped when null or empty
    • 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() and getUsableTunnelCount(), 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 clock
      expiryThreshold - 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

      private Boolean resolveRefreshForce(long now, boolean force)
      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 ms
      force - 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 set
      lastRefreshTime - 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, from Clock.now()
      Returns:
      true if refresh should be forced
    • getInboundTunnelsAsLeaseSet

      public LeaseSet getInboundTunnelsAsLeaseSet()
      Build and return current LeaseSet from our tunnels. Used by TunnelPoolManager to 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

      private void requestLeaseSet(LeaseSet ls)
      Request lease set from client for the primary and all aliases.
      Parameters:
      ls - non-null
    • requestLeaseSet

      private void requestLeaseSet(LeaseSet ls, boolean force)
      Request LeaseSet with optional force bypass of throttle.
      Parameters:
      ls - non-null
      force - 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

      protected LeaseSet 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

      private boolean isCachedLeaseSetValid(LeaseSet cached, long now)
      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 null
      now - the current time in ms, from the router's clock
      Returns:
      true if the cached LeaseSet may be reused instead of rebuilding one
    • removeRotatedOutTunnel

      private void removeRotatedOutTunnel(TunnelInfo rotated)
      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

      static int getTunnelAvgLatency(TunnelInfo t)
      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 stalls getActiveTunnelCount() and prevents the pool from building replacements via ensureSufficientTunnels().
    • pruneOverReserve

      private void pruneOverReserve(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.
      Parameters:
      toRemove - the prunable tunnels, non-empty
      goodCount - count of GOOD/FAILING tunnels in the pool
    • isPrunableNonGood

      private boolean isPrunableNonGood(TunnelInfo t, boolean isServerPool)
      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 consideration
      isServerPool - 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

      private int computeNonGoodReserve(List<TunnelInfo> toRemove, int goodCount)
      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 inflate size() 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 tunnels
      goodCount - the GOOD tunnels currently in the pool
      Returns:
      how many of toRemove to keep as LeaseSet fallback, 0 for a non-publishing pool
    • keepBestReserve

      private List<TunnelInfo> keepBestReserve(List<TunnelInfo> toRemove, int toPrune)
      Keep the reserve tunnels with the fewest failures, drop the worst toPrune.
      Parameters:
      toRemove - the candidates, sorted in place by consecutive failures
      toPrune - how many of the worst candidates to drop
      Returns:
      the tunnels left in the reserve, fewest failures first
    • pruneNonPublishedTunnels

      private void pruneNonPublishedTunnels(LeaseSet publishedLS)
      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

      private Set<Hash> collectPublishedGateways(LeaseSet publishedLS)
      Gateway hashes already used by the published lease set.
      Parameters:
      publishedLS - the LeaseSet that was just published
      Returns:
      the gateway hashes published in the LeaseSet, or null when it has no leases
    • 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 LeaseSet
      latencyThreshold - ms above which a non-published tunnel is too slow to keep
      now - 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 consideration
      publishedGateways - the gateways named by the published LeaseSet
      now - the current time in ms, from the router's clock
      latencyThreshold - 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 prune
      removeCount - how many tunnels the prune would remove
      Returns:
      true to skip
    • removeTunnelsBatch

      private void removeTunnelsBatch(List<TunnelInfo> toRemove)
      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_MARGIN so peers re-fetch while the gateway still routes; clamp to now + maxLeaseMs so nobody caches us for the full lifetime; and clamp up to now + 60s so 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, ms
      now - current time in ms
      maxLeaseMs - maximum advertised lease duration from now, ms
      Returns:
      the lease end date, always greater than now
    • buildLeaseFromTunnel

      private Lease buildLeaseFromTunnel(TunnelInfo cfg)
      Build a Lease from a single tunnel's gateway. The lease ends LEASE_SAFETY_MARGIN before 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

      private TunnelInfo findBestDegradedTunnel(List<TunnelInfo> tunnels, boolean isServerPool)
      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_MS remaining are eligible: the lease ends LEASE_SAFETY_MARGIN before 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 concurrently
      isServerPool - if true, don't skip getTunnelFailed() tunnels
      Returns:
      best candidate or null
    • isEligibleDegradedCandidate

      private boolean isEligibleDegradedCandidate(TunnelInfo t, boolean isServerPool, long now)
      Whether the tunnel may be replaced as a degraded candidate.
      Parameters:
      t - the tunnel under consideration
      isServerPool - true for a server pool, which may still use a tunnel marked failed
      now - 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 pass
      t - the tunnel compared against it
      bestFailures - the consecutive-failure count of the current best
      failures - 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, see BuildRequestor and BuildMessageGenerator.
      Returns:
      null on failure
    • countHealthySafe

      private static int countHealthySafe(TunnelPool.TunnelStats stats)
      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_THRESHOLD timeouts, 2 after SPARE_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 none
      now - current router time (ms)
      windowMs - window length (ms)
      Returns:
      start time to use for the next count — now when 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 0
      timeouts - 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.
      Parameters:
      rate - build-timeout rate in [0.0, 1.0], NaN for no data
      Returns:
      pre-build window in ms, in [180000L, 360000L]
    • 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 out
      now - 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

      private TunnelPool.TunnelStats sweepExpiredAndCountTunnels(long now, long preBuildThreshold)
      Drops expired tunnels and counts the rest.
      Parameters:
      now - the current time
      preBuildThreshold - cutoff for the pre-build decision
      Returns:
      the counts for the pool's tunnels
    • addStrandedTest

      private static void addStrandedTest(TunnelPool.TunnelStats stats, PooledTunnelCreatorConfig cfg)
      Collect a tunnel for the stranded re-offer, bounded by TestJob.MAX_STRANDED_OFFERS_PER_SWEEP per sweep.
      Parameters:
      stats - counters from the running sweep
      cfg - the tunnel to collect
    • removeTunnelFromPool

      private void removeTunnelFromPool(Iterator<TunnelInfo> it, TunnelInfo t)
      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 tunnel
      t - 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 pruned
      t - the UNTESTED tunnel under consideration
      stats - the sweep's counters, updated with the outcome
      now - the current time in ms, from the router's clock
      wallNow - the current wall-clock time in ms, compared against the tunnel's last transfer timestamp
      preBuildThreshold - 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 minLifeMs of life remains
    • scheduleLastChanceTests

      private void scheduleLastChanceTests(TunnelPool.TunnelStats stats)
      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

      private void offerStrandedTests(TunnelPool.TunnelStats stats)
      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

      private boolean isProtectedInUse(TunnelInfo t, long wallNow)
      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 as clearFailingOnTraffic().
      Parameters:
      t - the UNTESTED tunnel under consideration
      wallNow - 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() and getHealthyTunnelCount() 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

      private void countLiveTunnel(TunnelInfo t, TunnelPool.TunnelStats stats, long preBuildThreshold)
      Count a live non-UNTESTED tunnel: safe when it outlives the pre-build window, else near expiry.
      Parameters:
      t - the tunnel to classify
      stats - the sweep's counters, incremented for the tunnel
      preBuildThreshold - the ms timestamp ending the pre-build window: a tunnel expiring after it counts as safe
    • logCleanupSummary

      private void logCleanupSummary(TunnelPool.TunnelStats stats)
      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 cycle
      effectiveTarget - the boosted target including the emergency counter
      target - 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) use min(target+2, 8) so recovery is faster than the old hard cap of MAX_BUILD_PER_POOL_DIR while still bounding the buildComplete → ensureSufficientTunnels feedback loop.
      Parameters:
      safeActive - the count of safe, non soft-degraded tunnels in the pool
      target - the base tunnel count from the pool's configuration
      inProgress - 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 pool
      target - the base tunnel count from the pool's configuration
      inProgress - the builds currently in flight
      timeoutRate - 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 flight
      safeActive - the count of safe tunnels, 0 during a total collapse
      now - 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 cycle
      target - the base tunnel count from the pool's configuration
      effectiveTarget - the boosted target including the emergency counter
      now - 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 zero lastBuild (never built) is never throttled.

      Parameters:
      now - current router time (ms)
      lastBuild - last deficit-build timestamp (ms), 0 if never
      healthySafe - healthy (non soft-degraded) safe tunnel count
      safeActive - all safe tunnels including soft-degraded
      inProgress - builds currently in flight
      incompleteLeaseSet - 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 never
      safeActive - all safe tunnels including soft-degraded
      inProgress - builds currently in flight
      incompleteLeaseSet - 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 never
      usableTunnelCount - 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 never
      usableTunnelCount - current usable tunnel count (includes soft-degraded)
      healthyCount - usable tunnels below SOFT_DEGRADED_FOR_ENSURE
      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, 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 never
      usableTunnelCount - current usable tunnel count (includes soft-degraded)
      healthyCount - usable tunnels below SOFT_DEGRADED_FOR_ENSURE
      incompleteLeaseSet - true when the pool cannot publish a full LeaseSet
      lastRemoval - 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 never
      usableTunnelCount - current usable tunnel count (includes soft-degraded)
      healthyCount - usable tunnels below SOFT_DEGRADED_FOR_ENSURE
      incompleteLeaseSet - true when the pool cannot publish a full LeaseSet
      lastRemoval - last tunnel-removal timestamp (ms), 0 if never
      inProgress - builds currently in flight, or -1 when unknown
      Returns:
      true if ensureSufficientTunnels() should return early
    • isDeficitBuildNeeded

      private boolean isDeficitBuildNeeded(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. 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 pass
      effectiveTarget - 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 pass
      target - base effective tunnel count
      effectiveTarget - target plus emergency/failure boost
      currentInProgress - 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 tunnels
      safeActive - all safe tunnels (includes soft-degraded)
      softDegraded - safe tunnels at or above SOFT_DEGRADED_FOR_ENSURE
      failingCount - hard-failing tunnels (not in safeActive)
      untestedCount - untested tunnels counted against the deficit
      target - base effective tunnel count
      effectiveTarget - target plus emergency/failure boost
      currentInProgress - 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 cycle
      collapsed - true when the pool has no usable tunnels at all
      Returns:
      true to skip the proactive builds while the executor is in backoff
    • relaxWindowForStarvation

      private void relaxWindowForStarvation(RouterContext ctx)
      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

      private void handleEmergencyIfNeeded(TunnelPool.TunnelStats stats, int target)
      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 cycle
      target - the base tunnel count from the pool's configuration
    • isEmergencySituation

      private boolean isEmergencySituation(TunnelPool.TunnelStats stats)
      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 for getValidTunnelCount() — 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 ms
      usableCount - usable tunnel count at snapshot time
      inProgressCount - 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

      private boolean shouldSkipDueToPairedPool(TunnelPool.TunnelStats stats)
      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 least getLeaseViabilityWindow(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 at MAX_BUILD_PER_POOL_DIR in-flight builds to prevent the feedback loop where failed builds re-trigger more builds without limit.
      Parameters:
      needed - how many replacement tunnels to start
      bypassPacing - 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

      private PooledTunnelCreatorConfig configureNewTunnel(boolean forceZeroHop)
      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

      PooledTunnelCreatorConfig configureNewTunnel(boolean forceZeroHop, Set<Hash> excludeFirstHops)
      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 tunnel
      excludeFirstHops - 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

      private List<Hash> 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. 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 peers
      cooldownCutoff - 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

      private TunnelInfo findReusableCandidateLocked(int len, long now, int idx)
      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 clock
      idx - how many earlier candidates to skip, 0 on the first try
      Returns:
      the candidate tunnel to reuse, or null when none is eligible
    • hasAllPeerRIs

      private boolean hasAllPeerRIs(List<Hash> peers)
      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

      private TunnelInfo findReusedPredecessor(TunnelInfo successor)
      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

      static TunnelInfo findReusableCandidate(List<TunnelInfo> tunnels, int len, long now, int idx)
      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 first
      len - minimum acceptable length, including ourselves
      now - current time, for the expiry window
      idx - index to start scanning at
      Returns:
      the first eligible tunnel, or null if none remains
    • peersEndpointFirst

      private static List<Hash> peersEndpointFirst(TunnelInfo ti)
      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

      private boolean hasPeerOnCooldown(List<Hash> peers, long cooldownCutoff)
      Whether any non-self peer in the list is on cooldown.
      Parameters:
      peers - the candidate peer list, endpoint first
      cooldownCutoff - 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

      private void recordPeerCooldowns(List<Hash> peers, long now)
      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 skipped
      now - the current time in ms, recorded as each peer's selection time
    • logSelectPeersFailure

      private void logSelectPeersFailure(TunnelPoolSettings settings)
      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

      static String noPeersSelectedLine(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.
      Parameters:
      destName - the destination nickname, or the bracketed hash truncation
      inbound - 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 before SELECT_PEERS_FAILURE_SILENCE_MS of uptime, then at most once per SELECT_PEERS_FAILURE_WARN_INTERVAL_MS per pool.
      Parameters:
      uptime - the router uptime in ms
      now - the current time in ms
      lastWarnTime - 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 per SEND_FAILURE_LOG_INTERVAL_MS. State changes are never rate limited: failWithCount(TunnelInfo, int) and tellProfileFailed(TunnelInfo) still run on every report, only the log line is suppressed.
      Parameters:
      now - the current time in ms
      lastLogTime - 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 priority
      peers - the selected peers, endpoint first
      now - the current time in ms, written into each hop's creation time
      expiration - the expiration in ms written into every hop and the config
      Returns:
      the new tunnel configuration
    • fastFailTbrTarget

      private boolean fastFailTbrTarget(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. 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 immediately
      cfg - 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

      private void logConfigDebug(PooledTunnelCreatorConfig cfg, List<Hash> peers)
      Logs a new tunnel's config and peers at debug level.
      Parameters:
      cfg - the tunnel's config
      peers - the chosen peers
    • buildComplete

      void buildComplete(PooledTunnelCreatorConfig cfg, BuildExecutor.Result result)
      Remove from the _inprogress list and call addTunnel() if result is SUCCESS. Updates consecutive build timeout count.
      Parameters:
      cfg - the completed tunnel configuration
      result - the build result
    • recordPeerCooldownOnReject

      private void recordPeerCooldownOnReject(PooledTunnelCreatorConfig cfg, BuildExecutor.Result result)
      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 blame
      result - the build outcome, only REJECT and BAD_RESPONSE record a cooldown
    • handleBuildResult

      private void handleBuildResult(PooledTunnelCreatorConfig cfg, BuildExecutor.Result result)
      Handle the build result: update the consecutive timeout counter and the paired tunnel profile per outcome.
      Parameters:
      cfg - the build config being reported
      result - 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 by BuildExecutor for 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

      private void updatePairedProfile(PooledTunnelCreatorConfig cfg, boolean success)
      Update the paired tunnel profiles by treating the build as a tunnel test.
      Parameters:
      cfg - the build for this tunnel, to lookup the paired tunnel
      success - 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

      private PooledTunnelCreatorConfig findPairedInPool(TunnelPool pool, TunnelId pairedGW)
      Look up the paired tunnel in the given pool.
      Parameters:
      pool - the paired pool, may be null
      pairedGW - 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 RTT
      paired - 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

      private boolean shouldAnalyzeFailure(TunnelInfo cfg)
      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

      private void analyzeFailurePattern(TunnelInfo cfg)
      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

      public String toString()
      Description of the pool and its settings.
      Overrides:
      toString in class Object
    • formatPoolIdentity

      public static String formatPoolIdentity(TunnelPoolSettings settings)
      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