java.lang.Object
net.i2p.router.tunnel.pool.BuildHandler
All Implemented Interfaces:
Runnable

public class BuildHandlerextends Object implements Runnable
Handle the received tunnel build message requests and replies, including sending responses and updating tunnel lists.
  • Field Details

    • _context

      private final RouterContext _context
      The router running these tunnels.
    • _log

      private final Log _log
      Logger for this class.
    • _manager

      private final TunnelPoolManager _manager
      The tunnel pools the built tunnels join.
    • _exec

      private final BuildExecutor _exec
      Executor running the build requests.
    • _buildMessageHandlerJob

      private final Job _buildMessageHandlerJob
      Job handling inbound build requests.
    • _buildReplyMessageHandlerJob

      private final Job _buildReplyMessageHandlerJob
      Job handling inbound build replies.
    • _inboundBuildMessages

      private final BlockingQueue<BuildHandler.BuildMessageState> _inboundBuildMessages
      Queue of received build messages.
    • _processor

      private final BuildMessageProcessor _processor
      Thread draining that queue.
    • _requestThrottler

      private final RequestThrottler _requestThrottler
      Throttles inbound build requests under load.
    • _throttler

      private final ParticipatingThrottler _throttler
      Throttles transit participation under load.
    • _buildReplyHandler

      private final BuildReplyHandler _buildReplyHandler
      Matches build replies to the requests they answer.
    • _idleTunnelMonitor

      private final IdleTunnelMonitor _idleTunnelMonitor
      Monitor expiring idle tunnels.
    • _banLogger

      private final BanLogger _banLogger
      Logger for peers banned by build handling.
    • _lookupKeys

      private final ConcurrentHashMap<Hash, AtomicInteger> _lookupKeys
      Distinct next-hop keys currently being resolved, each carrying the refcount of build requests waiting on it. Slots count KEYS, not requests: N builds missing the same hop attach to one shared search, because the netdb coalesces duplicate lookups into a single network round trip.
    • _pendingLookups

      private final ConcurrentLinkedDeque<BuildHandler.PendingLookup> _pendingLookups
      FIFO of deferred lookups waiting for a concurrent-lookup slot.
    • _isRunning

      private volatile boolean _isRunning
      True while the handler is running.
    • _startupLock

      private final Object _startupLock
      Lock serializing handler startup.
    • _explState

      private BuildHandler.ExplState _explState
      Which exploratory tunnels exist, if any.
    • _minVersionHonorCaps

      private final String _minVersionHonorCaps
      Oldest peer version that honors capacity flags.
      See Also:
    • PROP_SHOULD_THROTTLE

      private static final String PROP_SHOULD_THROTTLE
      Property turning the transit throttle on or off.
      See Also:
    • IS_SLOW

      private static final boolean IS_SLOW
      True on a machine too slow for the default queue size.
    • _maxQueue

      private static volatile int _maxQueue
      Most build messages queued before dropping.
    • PROP_MAX_QUEUE

      private static final String PROP_MAX_QUEUE
      Property overriding the build queue cap.
      See Also:
    • NEXT_HOP_LOOKUP_TIMEOUT

      private static final int NEXT_HOP_LOOKUP_TIMEOUT
      Budget for a next-hop lookup, in ms.
      See Also:
    • MAX_NEXT_HOP_LOOKUP_RETRIES

      static final int MAX_NEXT_HOP_LOOKUP_RETRIES
      Extra RI lookup attempts allowed after a next-hop lookup times out. A timeout here is a local netdb miss (floodfills slow, RI just evicted), not evidence the peer is bad — rejecting threw away builds the next attempt would have completed. One retry keeps the total inside the originator's request budget (BuildRequestor.getRequestTimeout(RouterContext), gated by shouldRetryLookup(int, long, long, int, int)).
      See Also:
    • PRIORITY

      private static final int PRIORITY
      Message priority for build replies.
      See Also:
    • MAX_LOOKUP_LIMIT

      private static final int MAX_LOOKUP_LIMIT
      Concurrent next-hop search ceiling. Deliberately NOT cores-scaled: a lookup slot is an I/O-bound wait on floodfill replies, not CPU work, so hardware size doesn't change how many can be in flight. Network load is bounded downstream by per-key search coalescing, IterativeSearchJob fan-out caps, and the recently-queried floodfill cooldowns. Overridable via i2p.tunnel.build.maxLookupLimit.
    • PROP_MAX_PENDING_LOOKUPS

      private static final String PROP_MAX_PENDING_LOOKUPS
      i2p.tunnel.build.maxPendingLookups property; see getMaxPendingLookups(RouterContext).
      See Also:
    • LOOKUP_DEADLINE_MARGIN_MS

      private static final long LOOKUP_DEADLINE_MARGIN_MS
      Grace added to the lookup timeout for the independent slot-release deadline, so the normal callbacks (which release earlier) win the race in the common case and the deadline only catches leaks.
      See Also:
    • STARTUP_LOOKUP_BOOST_MS

      private static final long STARTUP_LOOKUP_BOOST_MS
      Cold-start window for next-hop lookup concurrency: until this long after startup, the lookup limit uses its full configured ceiling, because the netDb miss rate is high while participating x pct would clamp concurrency to near the floor exactly when inbound builds need it most.
      See Also:
    • _cfgCtx

      private static volatile RouterContext _cfgCtx
      The context the cached config values belong to.
    • _cfgRefreshed

      private static volatile long _cfgRefreshed
      Time the cached config values were last read.
    • _cachedNextHopLookupTimeout

      private static volatile int _cachedNextHopLookupTimeout
      Cached next-hop lookup timeout, in ms.
    • _cachedMinLookupLimit

      private static volatile int _cachedMinLookupLimit
      Cached floor for concurrent RI lookups.
    • _cachedMaxLookupLimit

      private static volatile int _cachedMaxLookupLimit
      Cached cap for concurrent RI lookups.
    • _cachedPercentLookupLimit

      private static volatile int _cachedPercentLookupLimit
      Cached next-hop lookup share, in percent.
    • _cachedMaxPendingLookups

      private static volatile int _cachedMaxPendingLookups
      Cached cap on pending next-hop lookups.
    • _cachedMaxRequestFuture

      private static volatile long _cachedMaxRequestFuture
      Cached future clock skew a request may show, in ms.
    • _cachedMaxRequestAge

      private static volatile long _cachedMaxRequestAge
      Cached oldest accepted build request age, in ms.
    • _cachedMaxRequestAgeEcies

      private static volatile long _cachedMaxRequestAgeEcies
      Same for ECIES-encrypted requests, in ms.
    • _cachedJobLagLimitTunnel

      private static volatile long _cachedJobLagLimitTunnel
      Cached job lag past which requests are dropped, in ms.
    • _cachedMaxParticipatingTunnels

      private static volatile int _cachedMaxParticipatingTunnels
      Cached cap on tunnels offered for transit.
    • CONFIG_REFRESH_MS

      private static final long CONFIG_REFRESH_MS
      Age past which the cached config is re-read.
      See Also:
    • _tunedNextHopLookupTimeout

      private static volatile int _tunedNextHopLookupTimeout
      Tuned lookup-limit statics, -1 = use router config. Set by the Tuner when autotuning the corresponding build params.
    • _tunedMaxLookupLimit

      private static volatile int _tunedMaxLookupLimit
      Tuner-set lookup cap, -1 when untuned.
    • _tunedPercentLookupLimit

      private static volatile int _tunedPercentLookupLimit
      Tuner-set lookup share, -1 when untuned.
    • RATES

      private static final long[] RATES
      Rate periods shown in the throttle stats.
    • BAN_INFLIGHT_THRESHOLD_MS

      static final long BAN_INFLIGHT_THRESHOLD_MS
      Age below which a ban is treated as having landed during the build.

      A tunnel build takes tens of seconds, so a ban recorded seconds ago cannot have been visible to the peer selector that dispatched this request. Longer than that, the ban predates the request and selection handed out a peer the banlist already held.

      See Also:
    • BAN_DROP_WARN_INTERVAL_MS

      private static final long BAN_DROP_WARN_INTERVAL_MS
      Minimum gap between banned-next-hop rejection warnings, and the number suppressed in between.

      One line per rejection buried the real warnings: a starved pool can reject hundreds of builds a minute, all identical, which is exactly when the log is needed for something else.

      See Also:
    • _lastBanDropWarn

      private volatile long _lastBanDropWarn
      Time of the last ban-on-drop warning.
    • _banDropsSinceWarn

      private volatile int _banDropsSinceWarn
      Drops counted since that warning.
    • NEXT_HOP_REPLIED

      static final int NEXT_HOP_REPLIED
      Outcome of one next-hop contact, for the rate-limited WARN summary.
      See Also:
    • NEXT_HOP_TIMEOUT

      static final int NEXT_HOP_TIMEOUT
      Next hop did not answer within the hop-contact window.
      See Also:
    • NEXT_HOP_DROPPED

      static final int NEXT_HOP_DROPPED
      We declined the inbound build request.
      See Also:
    • NEXT_HOP_OUTCOMES

      private static final int NEXT_HOP_OUTCOMES
      Number of next-hop outcome counters.
      See Also:
    • NEXT_HOP_SUMMARY_INTERVAL_MS

      static final long NEXT_HOP_SUMMARY_INTERVAL_MS
      How often the next-hop summary may be logged, ms. The individual events fire hundreds of times a minute at this rate, so they stay at DEBUG and only the rolled-up ratio is worth a WARN.
      See Also:
    • _nextHopOutcomes

      private static final AtomicLong[] _nextHopOutcomes
      Counters for each next-hop lookup outcome.
    • _nextHopSummaryLog

      private static final AtomicLong _nextHopSummaryLog
      Time of the next next-hop summary log line.
  • Constructor Details

    • BuildHandler

      public BuildHandler(RouterContext ctx, TunnelPoolManager manager, BuildExecutor exec)
      Wires up the pool manager, executor and ban logger, and registers the tunnel build rate stats.
      Parameters:
      ctx - the router context supplying properties, logging and statistics
      manager - the pool manager this handler reports build failures and successes to
      exec - the executor that runs the build passes this handler queues work for
  • Method Details

    • setNextHopLookupTimeout

      public static void setNextHopLookupTimeout(int ms)
      Set the next-hop lookup timeout (called by Tuner).
      Parameters:
      ms - the per-lookup timeout in ms
    • setMaxLookupLimit

      public static void setMaxLookupLimit(int limit)
      Set the max concurrent RI lookup limit (called by Tuner).
      Parameters:
      limit - the max lookup limit
    • setPercentLookupLimit

      public static void setPercentLookupLimit(int pct)
      Set the percent lookup limit (called by Tuner).
      Parameters:
      pct - the lookup percent
    • getNextHopLookupTimeout

      public static int getNextHopLookupTimeout(RouterContext ctx)
      The next-hop lookup timeout in effect, tuned value if set else config.
      Parameters:
      ctx - the router context
      Returns:
      the per-lookup timeout in ms
    • getMaxLookupLimit

      public static int getMaxLookupLimit(RouterContext ctx)
      The max concurrent RI lookup limit in effect, tuned value if set else config.
      Parameters:
      ctx - the router context
      Returns:
      the max lookup limit
    • getPercentLookupLimit

      public static int getPercentLookupLimit(RouterContext ctx)
      The percent lookup limit in effect, tuned value if set else config.
      Parameters:
      ctx - the router context
      Returns:
      the lookup percent
    • refreshBuildConfig

      private static void refreshBuildConfig(RouterContext ctx)
      Refresh the cached build configuration from properties at most once per CONFIG_REFRESH_MS, or immediately when the context changes. Benign race: duplicate refreshes are idempotent writes.
      Parameters:
      ctx - the router context supplying the clock and properties
    • getMinLookupLimit

      private static int getMinLookupLimit(RouterContext ctx)
      Gives the floor for concurrent RI lookups.
      Parameters:
      ctx - the router being configured
      Returns:
      the floor for concurrent RouterInfo lookups
    • getMaxPendingLookups

      private static int getMaxPendingLookups(RouterContext ctx)
      Pending next-hop lookups allowed to wait for a free slot while their build request is still inside the originator's budget. Sized near the useful maximum - a full queue drains inside the staleness window (drain rate x window is roughly 200) - so bursts absorb instead of dying. Entries past the originator's budget are discarded by the staleness check either way.
      Parameters:
      ctx - router context, for config lookup
      Returns:
      the queue depth cap
    • lookupLimit

      static int lookupLimit(int numTunnels, int minLimit, int maxLimit, int percentLimit, long uptimeMs)
      Concurrent next-hop lookup limit for the current request.

      Within STARTUP_LOOKUP_BOOST_MS of startup the full ceiling applies regardless of participating count: the netDb is still filling, so cache misses dominate and the proportional formula would clamp concurrency to near its floor exactly when inbound builds queue up. Afterward, scale with transit population as before, floored at minLimit so a drained pool can't zero out lookups.

      Pure decision — safe for unit tests.

      Parameters:
      numTunnels - current participating (transit) tunnel count
      minLimit - configured floor for the proportional formula
      maxLimit - configured ceiling for the proportional formula
      percentLimit - percent of participating count usable as slots
      uptimeMs - router uptime in ms
      Returns:
      the concurrent lookup limit, never below minLimit
    • attachLookupKey

      static boolean attachLookupKey(ConcurrentHashMap<Hash, AtomicInteger> inFlight, Hash key, int limit)
      Attach a build request's next hop to the in-flight lookup set. Capacity counts DISTINCT keys: requests joining an already-attached key consume no extra slot, because the netdb coalesces duplicate lookups for one key into a single network round trip. Without this, N builds missing the same popular hop would burn N slots to run one search and starve unrelated keys.

      The limit is soft: racing handler threads may transiently overshoot by at most the thread count, which is harmless.

      Pure decision on its map argument — safe for unit tests.

      Parameters:
      inFlight - distinct next-hop keys currently being resolved
      key - next hop of this request
      limit - concurrent distinct-key ceiling
      Returns:
      true if attached and a lookup should be issued; false when the ceiling is reached and the request must queue
    • releaseLookupKey

      static void releaseLookupKey(ConcurrentHashMap<Hash, AtomicInteger> inFlight, Hash key)
      Release one build request's attachment to an in-flight lookup key, dropping the key entry once its last waiting request completes so released slots become visible to new arrivals.

      Pure decision on its map argument. Safe for unit tests.

      Parameters:
      inFlight - distinct next-hop keys currently being resolved
      key - next hop being released by one request
    • scheduleLookupDeadline

      private void scheduleLookupDeadline(BuildHandler.BuildMessageState state, Hash nextPeer, AtomicBoolean decremented, int attempts)
      Schedule an independent deadline that releases this request's lookup attachment if neither callback has done so by then.

      Slot release must never depend solely on the netdb callbacks: any silent path between attach and callback registration (a dropped job, a search that never schedules, a constructor failure) would leak the key forever, pinning the distinct-key set at its ceiling and - since the pending queue drains only from release callbacks - freezing the queue full until restart. Observed live as exactly that deadlock. With this deadline, every attachment is guaranteed to release within attempts × lookupTimeout + margin, so leaks self-heal and the queue always has a drain trigger. The attempt count covers the bounded retry chain (MAX_NEXT_HOP_LOOKUP_RETRIES) so the deadline cannot reclaim the slot while a retry attempt is still in flight.

      Parameters:
      state - the build request state carrying the per-request released flag shared with the callbacks
      nextPeer - the attached next hop
      decremented - exactly-once flag shared with HandleReq/TimeoutReq
      attempts - total lookup attempts covered (initial + retries)
    • shouldRetryLookup

      static boolean shouldRetryLookup(int retriesLeft, long lookupStartedMs, long nowMs, int lookupTimeoutMs, int requestTimeoutMs)
      Whether a timed-out next-hop lookup deserves one more attempt. Retry only when a full extra lookup still fits inside the originator's build-request budget. Past that, the originator has given up and completing the join is wasted work (same reasoning as pendingLookupMaxAge(long, long)).
      Parameters:
      retriesLeft - remaining retries for this request
      lookupStartedMs - wall-clock time the first lookup started, 0 if unknown
      nowMs - current wall-clock time
      lookupTimeoutMs - per-lookup timeout (ms)
      requestTimeoutMs - originator's build request timeout (ms)
      Returns:
      true when another full attempt fits in the remaining budget
    • pendingLookupMaxAge

      static long pendingLookupMaxAge(long requestTimeoutMs, long nextHopLookupTimeoutMs)
      How long a queued next-hop lookup may wait before it is discarded as stale. Capped by the originator's build request timeout less the lookup timeout: past that budget the originator has given up, so completing the join is wasted work, while discarding earlier abandons builds that could still have succeeded within the originator's budget.
      Parameters:
      requestTimeoutMs - the originator's build request timeout in ms
      nextHopLookupTimeoutMs - the per-lookup timeout in ms
      Returns:
      the pending entry max age in ms, always positive
    • getMaxRequestFuture

      private static long getMaxRequestFuture(RouterContext ctx)
      Gives the future clock skew a request may show.
      Parameters:
      ctx - the router being configured
      Returns:
      the skew, in ms
    • getMaxRequestAge

      private static long getMaxRequestAge(RouterContext ctx)
      Gives the oldest accepted build request age.
      Parameters:
      ctx - the router being configured
      Returns:
      the age, in ms
    • getMaxRequestAgeEcies

      private static long getMaxRequestAgeEcies(RouterContext ctx)
      Same age limit for ECIES-encrypted requests.
      Parameters:
      ctx - the router being configured
      Returns:
      the age, in ms
    • getJobLagLimitTunnel

      private static long getJobLagLimitTunnel(RouterContext ctx)
      Gives the job lag past which tunnel requests are dropped.
      Parameters:
      ctx - the router being configured
      Returns:
      the lag, in ms
    • getMaxParticipatingTunnels

      private static int getMaxParticipatingTunnels(RouterContext ctx)
      Upper bound on participating tunnels before the router drops incoming build requests, tunable via router.maxParticipatingTunnels.
      Parameters:
      ctx - the router context whose properties are cached
      Returns:
      the cached limit on participating tunnels
    • formatBandwidth

      private static String formatBandwidth(int bps)
      Formats a rate for the stats display.
      Parameters:
      bps - the rate, in bytes per second
      Returns:
      the text to show
    • getMaxQueue

      public static int getMaxQueue()
      Gets the current cap on the build message queue.
      Returns:
      the cap on queued build messages
    • setMaxQueue

      public static void setMaxQueue(int val)
      Sets the cap on the build message queue.
      Parameters:
      val - the requested cap, clamped to the range 16 to 2048
    • init

      void init()
      Call the same time you start the threads.
    • restart

      public void restart()
      Drops any queued inbound build messages left over from before the restart.
    • shutdown

      public void shutdown(int numThreads)
      Cannot be restarted.
      Parameters:
      numThreads - the number of threads to be shut down
    • run

      public void run()
      Thread to handle inbound requests.
      Specified by:
      run in interface Runnable
    • banSeverity

      static String banSeverity(long banAgeMs)
      Classify why a next-hop peer is banlisted, for the rejection log.

      Pure and static so the classification is testable without a banlist. Keyed on how long the banlist has held the peer (Banlist.getBanAge), not on the ban's duration class: a peer permanently banned mid-flight and one banned last week both report as "permanent", so the duration class cannot distinguish the case that points at peer selection from the case that points at a selection-time cooldown.

      Parameters:
      banAgeMs - age of the standing ban, ms; negative when unknown
      Returns:
      a short label for the log line, never null
    • handleInboundRequest

      private void handleInboundRequest()
      Blocking call to handle a single inbound request.
    • handleReply

      private void handleReply(BuildHandler.BuildReplyMessageState state)
      Blocking call to handle a single inbound reply.
      Parameters:
      state - the received reply message with its receive time
    • handleReply

      private void handleReply(TunnelBuildReplyMessage msg, PooledTunnelCreatorConfig cfg, long delay)
      Blocking call to handle a single inbound reply.
      Parameters:
      msg - the reply message whose records report each hop's result
      cfg - the building tunnel config this reply was matched to
      delay - ms this reply waited in the queue before being handled, 0 or more
    • handleRequest

      private long handleRequest(BuildHandler.BuildMessageState state, long now)
      Decrypt the request, lookup the RI locally, and call handleReq(RouterInfo, BuildMessageState, BuildRequestRecord, Hash) if found or queue a lookup job.
      Parameters:
      state - the received build message with sender and receive time
      now - the current time in ms, compared against the receive time
      Returns:
      handle time or -1 if it wasn't completely handled
    • handleRequestAsInboundEndpoint

      private void handleRequestAsInboundEndpoint(BuildHandler.BuildEndMessageState state)
      This request is actually a reply, process it as such.
      Parameters:
      state - the received end message holding the reply records
    • drainPendingLookups

      private void drainPendingLookups()
      Drain the pending lookup queue, starting lookups for entries while we have capacity. Discard stale entries.
    • createRejectStats

      static void createRejectStats(StatManager mgr, long[] periods)
      Register the per-reason counters for build rejections we send back.

      Split out from the constructor so a test can prove that every name rejectStatName() hands to addRateData() is registered here. An unregistered name is dropped silently by StatManager.addRateData(), which is how the earlier "tunnel.reject." + response form measured nothing for its whole life.

      Parameters:
      mgr - the stat manager to register the counters with
      periods - the rate periods, normally RateConstants.SHORT_TERM_RATES
    • rejectStatName

      static String rejectStatName(int response)
      Stat name for the rejection reason we answer a build request with, or null if that reason has no stat.

      A table rather than "tunnel.reject." + response: the concatenated names were never registered, so StatManager.addRateData() dropped every write and the router had no count of why it was rejecting builds, while the concatenation allocated a string per rejection on a path a remote peer can inflate.

      Parameters:
      response - the TunnelHistory reject code
      Returns:
      the registered stat name, or null if the code has none
    • handleReq

      private void handleReq(RouterInfo nextPeerInfo, BuildHandler.BuildMessageState state, BuildRequestRecord req, Hash nextPeer)
      Actually process the request and send the reply.

      Todo: Replies are not subject to RED for bandwidth reasons, and the bandwidth is not credited to any tunnel. If we did credit the reply to the tunnel, it would prevent the classification of the tunnel as 'inactive' on tunnels.jsp.

      Parameters:
      nextPeerInfo - the local RouterInfo of the next hop to forward toward
      state - the received build message with sender hash and receive time
      req - the decrypted request record to act on
      nextPeer - hash of the next hop this request is addressed to
    • getInboundBuildQueueSize

      public int getInboundBuildQueueSize()
      The number of inbound build messages queued.
      Returns:
      the inbound build queue size
    • countNextHopOutcome

      static boolean countNextHopOutcome(int outcome, Log log, long now)
      Count one next-hop outcome and, at most once per NEXT_HOP_SUMMARY_INTERVAL_MS, log the ratio at WARN.

      This exists because the failure was invisible: 42% of handled inbound builds hit the hop-contact timeout, but the only record was one DEBUG line per event, so an eepsite silently stopped serving and nothing at WARN reflected it. The rate is the diagnostic signal, not any single event — one slow peer is noise, a sustained third of builds unanswered is a reachability problem.

      Parameters:
      outcome - one of NEXT_HOP_REPLIED, NEXT_HOP_TIMEOUT, NEXT_HOP_DROPPED
      log - the log to write to, may be null
      now - current time in ms
      Returns:
      true if a summary was logged
    • snapshotNextHopOutcomes

      static long[] snapshotNextHopOutcomes()
      Current next-hop outcome counts.
      Returns:
      [replied, timedOut, dropped]
    • resetNextHopOutcomes

      static void resetNextHopOutcomes()
      Reset the counters, for tests.
    • nextHopSummaryText

      static String nextHopSummaryText(long[] counts, long windowMs)
      Format the next-hop summary line. Pure so the wording and the division are testable without a live router, and so the denominator is explicit: the timeout percentage is of handled requests, not of all outcomes.
      Parameters:
      counts - [replied, timedOut, dropped]
      windowMs - window the counts cover
      Returns:
      a WARN summary on two lines, plus a third naming the suspect when the unanswered rate reaches the flag threshold
    • getIPPortFromHash

      private String getIPPortFromHash(Hash h)
      Extract IP:port from RouterInfo for a peer hash.
      Parameters:
      h - the peer hash to look up locally, unvalidated
      Returns:
      host:port of the first usable address, or "UNKNOWN" if the RouterInfo is not stored locally
    • _x

      private static final String _x(String s)
      Mark a string for extraction by xgettext and translation. Use this only in static initializers. It does not translate!
      Parameters:
      s - the string to mark for extraction
      Returns:
      s unchanged; marking happens by side effect only