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
    (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.
    (package private) static final class 
    Why a pool could not supply a tunnel, as counts rather than prose.
    (package private) static enum 
    Why a tunnel's local transport session was lost.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    protected boolean
    Whether this pool is running.
    protected final RouterContext
    The router context.
    protected final Log
    The log.
    (package private) static final long
    Default early expiration time for pruned tunnels (120 seconds).
    (package private) static final long
    Rolling average test duration below which the test window is narrowed.
    (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.
    (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.
    (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.
    (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.
    (package private) static final double
    Pre-emergency threshold: fraction of target below which builds start immediately.
    (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.
    (package private) static final double
    Priority weight for service pool builds.
    (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)).
    (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
    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.
    (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.
    (package private) boolean
    This will build a fallback (zero-hop) tunnel ONLY if this pool is exploratory, or the settings allow it.
    (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.
    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.
    (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.
    (package private) static int
    computeDifficultySpare(int consecutiveBuildTimeouts)
    Spare tunnels held above target while the pool is having difficulty building.
    (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.
    (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.
    (package private) PooledTunnelCreatorConfig
    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.
    (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.
    (package private) int
    Count the zero-hop fallback tunnels in this pool.
    (package private) void
    Ensure the pool has at least target valid tunnels, building replacements proactively when the count drops below target.
    (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.
    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.
    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.
    (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.
    (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.
    (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.
    (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.
    (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.
    (package private) static boolean
    isEmergencyCooldownActive(long elapsedMs, long usableCount, long inProgressCount)
    Whether the emergency-cooldown window has elapsed.
    (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).
    (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.
    (package private) static boolean
    isMateriallyShort(int leases, int wanted)
    Whether a published LeaseSet is too small to count as a working set.
    (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.
    (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.
    (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.
    protected LeaseSet
    Build a leaseSet with the required tunnels that aren't about to expire.
    (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.
    (package private) boolean
    Do we really need more fallbacks? Used to prevent a zillion of them.
    (package private) void
    Called by TestJob when a server pool tunnel fails a test but is retained.
    (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.
    (package private) void
    Proactively republish the LeaseSet when all tunnels are healthy (all > 5 min expiry).
    int
    Prune excess tunnels from the pool to stay within budget.
    (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.
    (package private) void
    Remove a config that was never actually sent to the network (e.g.
    (package private) void
    Remove a tunnel from the pool.
    (package private) boolean
    Synchronous tunnel removal for use during recovery or critical situations.
    (package private) void
    reportSendFailure(TunnelInfo cfg, int status)
    Report a data-phase dispatch failure for a tunnel in this pool.
    void
    Request new inbound tunnels so the pool holds the target count with at least getLeaseViabilityWindow(RouterContext) remaining.
    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.
    (package private) static long
    rotateWindowStart(long windowStart, long now, long windowMs)
    Roll the build-rate window forward when it has expired.
    (package private) TunnelInfo
    Pull a random tunnel out of the pool.
    (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.
    (package private) void
    Paired pool (inbound <-> outbound for same destination).
    (package private) void
    Update the settings for this pool.
    (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.
    (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.
    (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.
    (package private) static boolean
    shouldLogSendFailure(long now, long lastLogTime)
    Whether a data-phase failure or condemnation line should be logged now.
    (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.
    (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.
    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.
    (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.
    (package private) static double
    timeoutRate(long attempts, long timeouts)
    Fraction of counted build attempts that timed out.
    Description of the pool and its settings.
    (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.
    (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.
    (package private) static int
    usableLeaseMinimum(int wanted)
    The smallest LeaseSet treated as usable for a given target.
    (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

    • 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.
    • _context

      protected final RouterContext _context
      The router context.
    • _log

      protected final Log _log
      The log.
    • _alive

      protected volatile boolean _alive
      Whether this pool is running.
    • 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:
    • 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:
    • 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:
    • 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:
    • 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:
    • DEFAULT_PRUNE_EARLY_EXPIRY

      static final long DEFAULT_PRUNE_EARLY_EXPIRY
      Default early expiration time for pruned tunnels (120 seconds).
      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:
  • 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

    • 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).
    • 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
    • getAvgBWPerTunnel

      public int getAvgBWPerTunnel()
      Average bandwidth per tunnel in the pool.
      Returns:
      average bandwidth per configured tunnel in Bps
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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) for budget-based pruning instead of direct removal.
      Returns:
      number of tunnels marked for removal
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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.

    • 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)
    • 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
    • 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.
    • 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
    • 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
    • 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
    • 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
    • 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]
    • 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
    • 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
    • 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).

    • 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
    • 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
    • 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
    • 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
    • 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.
    • 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.
    • 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
    • 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
    • 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
    • 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
    • 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.
    • 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
      Returns:
      formatted identity string