java.lang.Object
net.i2p.router.JobImpl
net.i2p.router.networkdb.kademlia.RepublishLeaseSetJob
All Implemented Interfaces:
Job

public class RepublishLeaseSetJobextends JobImpl
Periodically republishes a local LeaseSet to the network database.

Handles the lifecycle of lease set publication including:

  • Initial publication after sufficient uptime (deferred until target tunnels built)
  • Periodic republishing before lease expiration (default 5 min)
  • On-success global fail count reset
  • Retry with exponential backoff on failure
  • Floodfill verification after repeated failures
  • Cleanup when the client is no longer local

Thread-safe via concurrent maps across instances.

  • Field Details

    • _log

      private final Log _log
      Class logger.
    • PROP_TIMEOUT

      private static final String PROP_TIMEOUT
      Config property for LeaseSet publish timeout.
      See Also:
    • PROP_RETRY_DELAY

      private static final String PROP_RETRY_DELAY
      Config property for publish retry base delay.
      See Also:
    • PROP_MAX_RETRY_DELAY

      private static final String PROP_MAX_RETRY_DELAY
      Config property for max publish retry delay.
      See Also:
    • REPUBLISH_LEASESET_TIMEOUT_DEFAULT

      public static final long REPUBLISH_LEASESET_TIMEOUT_DEFAULT
      Default timeout for LeaseSet publication to floodfills.
      See Also:
    • RETRY_DELAY_DEFAULT

      public static final int RETRY_DELAY_DEFAULT
      Default base delay before retrying a failed publish.
      See Also:
    • RETRY_MAX_DELAY_DEFAULT

      public static final int RETRY_MAX_DELAY_DEFAULT
      Maximum backoff delay for publish retries.
      See Also:
    • EXPIRY_WINDOW

      private static final long EXPIRY_WINDOW
      Window before lease expiry to trigger a re-mint instead of flooding the dying copy. Must stay below (lease cap - republish interval) — here 240s < 600s - 300s — so a freshly re-minted copy (capped at ~600s) still presents above this window at the next interval check and gets flooded directly; with an equal boundary the check lands at the knife edge and re-mints forever, starving the floodfill.
      See Also:
    • MIN_RESCHEDULE

      private static final long MIN_RESCHEDULE
      Minimum reschedule interval — prevents sub-minute flood treadmills.
      See Also:
    • LEASE_SUPPLY_WINDOW

      private static final long LEASE_SUPPLY_WINDOW
      How long before a LeaseSet expires we start insisting the pool holds genuinely fresh tunnels, rather than minting whatever is on hand.

      The mint used to be purely reactive: expiry came into range, we asked the pool for a copy and signed whatever it held. Since a LeaseSet's date is the earliest lease end, one near-floor lease caps the whole copy, which is how published sets ended up at 76-120s. Starting the supply check this far ahead leaves time to wait for a build.

      Three minutes is chosen against EXPIRY_WINDOW (4m) so the check engages one interval before the renew path does, and against EMERGENCY_REMINT_WINDOW (2m) so there is a full minute of pre-flight before the mint is allowed to become thin.

      See Also:
    • MAX_SUPPLY_DEFERRALS

      private static final int MAX_SUPPLY_DEFERRALS
      Consecutive supply checks a destination may defer before minting anyway.
      See Also:
    • LEASE_ELIGIBILITY_FLOOR_MS

      static final long LEASE_ELIGIBILITY_FLOOR_MS
      The pool's own lease-eligibility floor, mirrored from TunnelPool.LEASE_MIN_REMAINING_MS. A tunnel with less than this remaining is refused for publication, so a LeaseSet minted from a healthy pool cannot present below it.

      This is the correct yardstick for the anomaly check. An earlier version compared against the ~600s design target, which is unreachable in practice: a LeaseSet's expiry is the minimum across its leases, so holding 600s requires every lease to be simultaneously fresh, and the target sat far above the floor. That combination made the check true for essentially every LeaseSet on every pass — 129 warnings in twelve minutes — which is log volume, not a fault signal.

      See Also:
    • SHORT_EXPIRY_LOG_INTERVAL

      private static final long SHORT_EXPIRY_LOG_INTERVAL
      Minimum gap between short-expiry warnings for one destination.
      See Also:
    • _lastShortExpiryLog

      private static final ConcurrentHashMap<Hash,Long> _lastShortExpiryLog
      Last short-expiry warning per destination, for rate limiting.
    • _supplyDeferrals

      private static final ConcurrentHashMap<Hash,Integer> _supplyDeferrals
      Consecutive supply-check deferrals per destination, bounded so a pool that never reaches the freshness threshold cannot defer its LeaseSet forever.
    • EMERGENCY_REMINT_WINDOW

      private static final long EMERGENCY_REMINT_WINDOW
      Emergency re-mint window: when the stored copy is within this much of expiring, re-mint whatever the pool holds down to a single viable lease, skipping the normal required-count gate. A thin-but-alive public LeaseSet beats letting the copy lapse while the deferral waits for capacity, and every lease the pool hands back is newer than the dying copy's (the 10-minute lease cap guarantees a later earliest-lease-date), so the floodfill accepts and re-floods the rescue copy. Matches the pool's own lease-eligibility floor (LEASE_MIN_REMAINING_MS = 2 min): a lease the pool refuses to publish cannot outlive floodfill propagation, so once we are inside that floor there is no further capacity to wait for.
      See Also:
    • MISSING_LEASESET_RETRY

      private static final long MISSING_LEASESET_RETRY
      Retry interval after a missing local LeaseSet — long enough for the pool to build and test a replacement tunnel before we ask again.
      See Also:
    • MAX_REMINT_DEFERS

      private static final int MAX_REMINT_DEFERS
      Consecutive fresh-build deferrals before falling back to a thin re-mint.
      See Also:
    • CACHE_CLEANUP_THRESHOLD

      private static final long CACHE_CLEANUP_THRESHOLD
      Staleness threshold for cleaning up tracking maps.
      See Also:
    • _lastCleanupTime

      private static volatile long _lastCleanupTime
      When the stale entry cleanup last ran, to guard against redundant sweeps.
    • _retryInProgress

      private static final ConcurrentHashMap<Hash,Boolean> _retryInProgress
      Tracks destinations currently being retried to prevent duplicate retries.
    • _lastPublishLogTime

      private static final ConcurrentHashMap<Hash,Long> _lastPublishLogTime
      Last publish attempt per destination. Doubles as the throttle for publish log messages and as the liveness set the stale-entry cleanups key off, so it is written regardless of log level.
    • PUBLISH_LOG_THROTTLE

      private static final long PUBLISH_LOG_THROTTLE
      Minimum interval between repeated publish log messages for one destination.
      See Also:
    • _lastVerifyLogTime

      private static final ConcurrentHashMap<Hash,Long> _lastVerifyLogTime
      Last log timestamp per destination to throttle verification log messages.
    • _lastNotRequeueLogTime

      private static final ConcurrentHashMap<Hash,Long> _lastNotRequeueLogTime
      Last log timestamp per destination to throttle no-requeue log messages.
    • _lastMissingLogTime

      private static final ConcurrentHashMap<Hash,Long> _lastMissingLogTime
      Last log timestamp per destination to throttle missing-LeaseSet log messages.
    • _globalFailCount

      private static final ConcurrentHashMap<Hash, AtomicInteger> _globalFailCount
      Persistent per-destination store failure count, never reset, surviving job instances, used to decide when to do the expensive floodfill verification.
    • _remintDefers

      private static final ConcurrentHashMap<Hash, AtomicInteger> _remintDefers
      Consecutive re-mint deferral count per destination, to bound fresh-build waiting.
    • _lastIneffectiveRemint

      private static final ConcurrentHashMap<Hash,Long> _lastIneffectiveRemint
      When this destination last ran a re-mint that could not improve anything. @since 0.9.71+.
    • MAX_FLOODFILL_VERIFICATIONS

      private static final int MAX_FLOODFILL_VERIFICATIONS
      Max verifications per destination before falling back to direct retry.
      See Also:
    • _firstDeferredAt

      private static final ConcurrentHashMap<Hash,Long> _firstDeferredAt
      When the first publish was deferred, per destination, cleared on success or on the first publish timeout.
    • FIRST_PUBLISH_TIMEOUT

      private static final long FIRST_PUBLISH_TIMEOUT
      Max time to defer first publish while waiting for target tunnels.
      See Also:
    • _publishedOnce

      private static final Set<Hash> _publishedOnce
      Destinations that have published successfully at least once — never defer these again.
    • _dest

      private final Hash _dest
      Destination hash.
    • _facade

      private final KademliaNetworkDatabaseFacade _facade
      Kademlia network database facade instance.
    • _lastPublished

      private volatile long _lastPublished
      Last published time.
    • failCount

      private final AtomicInteger failCount
      Per-instance retry counter for exponential backoff.
    • highPriority

      private boolean highPriority
      Whether this job instance is promoted to high priority.
    • _lookupInProgress

      private final AtomicBoolean _lookupInProgress
      Whether a floodfill verification lookup is in progress.
    • _registered

      private boolean _registered
      Registered flag.
  • Constructor Details

    • RepublishLeaseSetJob

      public RepublishLeaseSetJob(RouterContext ctx, KademliaNetworkDatabaseFacade facade, Hash destHash)
      Creates a new job to republish a LeaseSet for the given destination.
      Parameters:
      ctx - the router context
      facade - the network database facade
      destHash - the destination hash to republish
  • Method Details

    • effectiveExpiryAnomalouslyShort

      static boolean effectiveExpiryAnomalouslyShort(long effectiveExpiry)
      Whether a LeaseSet expiry is short enough to indicate the inbound pool could not supply a viable lease.

      The threshold is the pool's eligibility floor, not the design target: presenting at or below the floor means no lease in the copy had the minimum remaining life the pool requires to publish, which is the actual fault this signal exists to name. Anything above the floor is ordinary decay and must stay silent, or the signal costs log volume while pointing at nothing.

      Parameters:
      effectiveExpiry - ms until the copy currently in effect expires
      Returns:
      true if the pool could barely qualify any lease in the copy
    • getLeaseViabilityWindow

      private static long getLeaseViabilityWindow(RouterContext ctx)
      A lease is viable for a re-mint while it has at least this much time remaining: the re-sign request, floodfill propagation, and connection establishment all need the lease alive, and a tunnel closer to expiry than this is already inside the pool's proactive replacement window, so counting it would overstate the pool's health. A healthy pool therefore reads close to its target count, and re-mints proceed without requesting extra builds.
      Parameters:
      ctx - the router context
      Returns:
      the lease viability window in ms
    • registerSelf

      public boolean registerSelf()
      Attempt to register this job with the facade.
      Returns:
      true if registered, false if a job was already active
    • runJob

      public void runJob()
      Republish the LeaseSet if this job is still registered, then reschedule. An unregistered job has been superseded and does nothing.
    • handleShouldNotPublish

      private void handleShouldNotPublish()
      Handles the client asking to stop publishing this lease set.
    • handleLocalLeaseSet

      private void handleLocalLeaseSet()
      Handles a local lease set that needs publication.
    • handleExpiredLeaseSet

      private void handleExpiredLeaseSet(LeaseSet ls, String name)
      Handle expired lease set: request a fresh one and check back soon.
      Parameters:
      ls - the expired lease set, named in the log by its hash
      name - the log suffix naming the destination, " for 'nickname'" or " for key"
    • handleMissingLeaseSet

      private void handleMissingLeaseSet()
      Handles a missing lease set by asking the client to create one.
    • handleNotLocal

      private void handleNotLocal()
      Handles a client that is no longer local, cleaning up and stopping publication.
    • isTrackedLocal

      private boolean isTrackedLocal()
      Determine if client is still a configured local tunnel even when its I2CP session is closed (close-on-idle), that is, a pool still exists.
      Returns:
      true if the destination is local, or has inbound or outbound tunnel settings
    • handleValidLeaseSet

      private void handleValidLeaseSet(LeaseSet ls, String name, long now, long timeUntilExpiry)
      Handle valid lease set: publish it. The startup gate defers the first publication until the target tunnel count is met, or FIRST_PUBLISH_TIMEOUT elapses. After the first successful publish, subsequent renewals proceed immediately.
      Parameters:
      ls - the unexpired lease set to publish
      name - the log suffix naming the destination, " for 'nickname'" or " for key"
      now - the current time in milliseconds
      timeUntilExpiry - milliseconds until the earliest lease of ls expires
    • maybeDeferFirstPublish

      private boolean maybeDeferFirstPublish(int leaseCount, int targetLeases)
      Whether publication was deferred while waiting for target tunnels. At startup the first publication is deferred until the target tunnel count is met; it falls through after FIRST_PUBLISH_TIMEOUT so pools at partial capacity (e.g. 70% build success) do not block indefinitely. One-time only: once a destination publishes, _publishedOnce prevents re-deferral on subsequent cycles (no 15s treadmill).
      Parameters:
      leaseCount - the leases the published lease set currently carries
      targetLeases - the inbound tunnel count the destination's pool is configured for
      Returns:
      true if publication was deferred
    • getName

      public String getName()
      Human-readable name for the job queue.
      Returns:
      "Republish Local LeaseSet" with optional high-priority marker
    • getPublishTimeout

      private long getPublishTimeout()
      The time allowed for one publish.
      Returns:
      the timeout, in milliseconds
    • getRetryDelay

      private int getRetryDelay()
      The delay before a retry.
      Returns:
      the delay, in milliseconds
    • getMaxRetryDelay

      private int getMaxRetryDelay()
      The largest retry delay allowed.
      Returns:
      the delay, in milliseconds
    • getRepublishInterval

      private long getRepublishInterval()
      The interval between republishes.
      Returns:
      the interval, in milliseconds
    • computeNextRepublish

      private long computeNextRepublish(long timeUntilExpiry)
      Next republish delay: the configured interval floored at MIN_RESCHEDULE, but capped so the next check fires before the current LeaseSet hits the EXPIRY_WINDOW re-mint threshold. A fresh 10-minute lease with a 5-min interval schedules at the interval; a shorter/thinner lease schedules sooner, so a re-mint that only slightly extends expiry is re-flooded again before the copy dies instead of rotting for a full interval. With the lease cap at 600s the alternation is: re-mint lands ~594s, the next check at the interval sees it above EXPIRY_WINDOW and floods directly, then the 60s re-check crosses the window and re-mints again — every cycle both refreshes and floodfills.
      Parameters:
      timeUntilExpiry - ms until the current LeaseSet expires
      Returns:
      delay in ms
    • getFreshPoolLeaseSet

      private LeaseSet getFreshPoolLeaseSet()
      The inbound pool's current tunnels as a re-mintable LeaseSet, or null if the client has no inbound pool or no usable leases.
      Returns:
      current-pool LeaseSet, or null
    • shouldRemint

      static boolean shouldRemint(int freshCount, int required, boolean extendsExpiry, long timeUntilExpiry, long emergencyRemintWindow)
      Whether a re-mint may proceed now using the pool's current LeaseSet.

      Two independent gates, either of which is enough:

      • Emergency backstop: the stored copy is inside EMERGENCY_REMINT_WINDOW and the pool holds at least one viable lease. Waiting costs nothing — every viable lease ends after the dying copy's, so the rescue copy strictly extends it — but deferring risks a fully-lapsed stored copy and the visible outage that follows while handleExpiredLeaseSet rebuilds.
      • Normal gate: outside the emergency window the re-mint proceeds only when the pool copy extends the stored copy and carries at least required viable leases, so the re-signed LeaseSet is not unnecessarily thin.
      A pool copy with no viable leases (all near-dead) never re-mints — re-signing it would not push expiry forward, so it pads nothing but latency; the expiry path's pool rebuild is the backstop for a fully lapsed copy.
      Parameters:
      freshCount - number of pool leases with a viability window remaining
      required - minimum viable leases for a normal (non-emergency) re-mint
      extendsExpiry - true if the pool copy's latest lease exceeds the stored copy's
      timeUntilExpiry - ms until the stored copy expires
      emergencyRemintWindow - the emergency threshold in ms
      Returns:
      true if the re-mint should be requested now
    • ineffectiveRemintDelay

      static long ineffectiveRemintDelay(long timeUntilExpiry)
      How long to hold off a further re-mint after one that changed nothing.

      A re-mint inside the emergency window is permitted even when it does not extend the stored copy, because a thin copy the pool can still serve beats letting the LeaseSet die. That exemption is what makes the loop possible: if the fresh copy expires no later than the stored one, publishing it does not move timeUntilExpiry, so the next cycle is still inside the emergency window and re-arms immediately. Measured in production that was roughly one re-mint per second per pool, all of it re-advertising the same two aging leases.

      The hold-off is deliberately not a fixed interval. It is half the time the stored copy has left, so however close to expiry the copy is, a further attempt is always scheduled before it can lapse. A fixed floor would have to be small enough for a nearly-dead copy and would then be needlessly slow for a healthy one: these LeaseSets are designed to last around ten minutes but were observed at sixty seconds, so no single constant is right for both. Five minutes, the obvious choice, would let a sixty-second copy lapse unrepublished for four minutes, which is the exact outage the emergency path exists to prevent.

      Always strictly less than timeUntilExpiry for any positive value, which is the invariant that matters: holding off can never be the reason a LeaseSet lapses unrepublished.

      Parameters:
      timeUntilExpiry - ms until the stored copy expires
      Returns:
      minimum ms to wait before another re-mint attempt
    • sameLeaseTunnels

      static boolean sameLeaseTunnels(LeaseSet stored, LeaseSet fresh)
      Whether two lease sets advertise exactly the same tunnels.

      Compares tunnel ids as a set, so a re-mint that merely reorders the same leases is recognised as a no-op. This is the loop's true cause: inside the emergency window the viability gate is skipped, so a pool that cannot replace its aging tunnels re-mints the identical set, the stored expiry does not move, the next pass is still inside the emergency window, and the cycle repeats. Measured in production that was ~170 re-mints per burst, six milliseconds apart.

      Parameters:
      stored - the currently published lease set, may be null
      fresh - the candidate from the pool, may be null
      Returns:
      true if both carry the same non-empty set of tunnel ids
    • refloatLeaseSet

      private void refloatLeaseSet(LeaseSet storedLeaseSet, String name, long now, long timeUntilExpiry)
      Re-publish the lease set before it expires from the network DB.
      Parameters:
      storedLeaseSet - the LeaseSet currently published, for no-op detection
      name - the log suffix naming the destination, " for 'nickname'" or " for key"
      now - the current time in milliseconds
      timeUntilExpiry - milliseconds until the earliest lease of the stored set expires
    • poolHasAnyViableLease

      private boolean poolHasAnyViableLease(LeaseSet fresh, long now)
      Whether the pool can sign anything at all right now.

      Deliberately a starvation test, not a sufficiency test. Being below the target lease count is an ordinary, already-handled condition: shouldRemint(int, int, boolean, long, long) defers on it and falls back to a thin copy after a bounded number of attempts. Re-deciding it here with a second threshold and a second counter meant the two mechanisms shadowed each other, and a pool that was merely one lease short could defer indefinitely.

      So the question here is strictly "is there anything publishable at all", read from the pool's own LeaseSet -- the same input the surrounding decision already uses -- rather than from raw tunnel state.

      Parameters:
      fresh - the pool's current LeaseSet, may be null
      now - current time in ms
      Returns:
      true when the pool has at least one lease still viable for a re-mint
    • countFreshLeases

      private int countFreshLeases(LeaseSet ls, long now)
      Counts the leases that are still fresh.
      Parameters:
      ls - the lease set to check
      now - the current time
      Returns:
      the number of fresh leases
    • getTargetLeaseCount

      private int getTargetLeaseCount()
      The number of inbound leases a re-mint should carry to be useful: the inbound tunnel quantity for a server pool, 1 otherwise.
      Returns:
      the target lease count
    • registerSuccessor

      private RepublishLeaseSetJob registerSuccessor(long delayMs)
      Schedules the next republish job.
      Parameters:
      delayMs - the delay before the next job
      Returns:
      the new job
    • scheduleRepublish

      private void scheduleRepublish(long delayMs)
      Schedule republish: queue the next republish cycle, unregistering the current job first so hasActiveRepublishJob() doesn't block the successor.
      Parameters:
      delayMs - how long from now the successor job should start, in milliseconds
    • requeueRepublish

      void requeueRepublish()
      Requeues the republish after a failed publish, with exponential backoff, an aggressive first retry, and every fourth attempt promoted to high priority.
    • clearRetryInProgress

      private void clearRetryInProgress()
      Clear retry in progress.
    • shortHash

      private String shortHash()
      The short hash of the local destination for logs.
      Returns:
      the first eight base32 characters
    • cleanupStaleEntries

      private static void cleanupStaleEntries()
      Cleanup stale entries.
    • cleanupPublishedOnce

      private static void cleanupPublishedOnce()
      Drop _publishedOnce entries for destinations no longer being tracked.
    • recordPublishAttempt

      static boolean recordPublishAttempt(ConcurrentHashMap<Hash,Long> times, Hash dest, Long previous, long now)
      Record that we tried to publish for this destination, and report whether the event is worth logging.

      The timestamp is written unconditionally. Several cleanups key off _lastPublishLogTime - cleanupPublishedOnce(), cleanupGlobalFailCount() and cleanupRemintDefers() - so writing it only inside a shouldInfo() guard left those maps permanently empty at the default log level, wiping the failure counters every cleanup cycle and leaving the escalation path unable to fire. State must never depend on the log level.

      Parameters:
      times - the per-destination last-log timestamps
      dest - the destination being published
      previous - the timestamp already recorded for it, or null
      now - current time in ms
      Returns:
      true if enough time has passed that the event should be logged
    • cleanupMap

      private static void cleanupMap(ConcurrentHashMap<Hash,Long> map, long now)
      Cleanup map: drop the entries whose timestamp is older than the cleanup threshold.
      Parameters:
      map - the destination to timestamp map to prune in place
      now - the current time in milliseconds, compared against each entry's timestamp
    • cleanupGlobalFailCount

      private static void cleanupGlobalFailCount(long now)
      Cleanup global fail count: reset the counters of destinations no longer being tracked, assuming their publication has finished.
      Parameters:
      now - the current time in milliseconds, unused because the counters carry no timestamps
    • cleanupRemintDefers

      private static void cleanupRemintDefers()
      Reset re-mint deferral counters for destinations no longer tracked.
    • lastPublished

      public long lastPublished()
      Timestamp of the last successful publication.
      Returns:
      timestamp in ms, or 0 if never published
    • getDestHash

      Hash getDestHash()
      The destination hash this job publishes for.
      Returns:
      the hash of the destination whose LeaseSet this job publishes
    • getTunnelName

      public String getTunnelName(Destination d)
      Look up the tunnel nickname for a destination.
      Parameters:
      d - the destination whose tunnel nickname is wanted
      Returns:
      tunnel nickname if configured, or the short base32 hash as fallback