java.lang.Object
net.i2p.i2ptunnel.TunnelControllerGroup
All Implemented Interfaces:
ClientApp

public class TunnelControllerGroupextends Object implements ClientApp
Coordinates tunnel groups within the JVM, managing configuration persistence.

Loads and stores tunnel configurations to disk, builds new tunnels on request. Entry point from clients.config.

  • Field Details

    • _log

      private final Log _log
      Logger for the tunnel controllers.
    • _state

      private volatile ClientAppState _state
      Current state of the tunnel apps.
    • _context

      private final I2PAppContext _context
      The app context.
    • _mgr

      private final ClientAppManager _mgr
      The manager that starts the apps.
    • instance

      private static volatile TunnelControllerGroup instance
      The single instance of the group.
    • DEFAULT_CONFIG_FILE

      static final String DEFAULT_CONFIG_FILE
      Default config file name.
      See Also:
    • CONFIG_DIR

      private static final String CONFIG_DIR
      Config directory for split config files.
      See Also:
    • PREFIX

      private static final String PREFIX
      Config key prefix.
      See Also:
    • _controllers

      private final List<TunnelController> _controllers
      The client and server controllers.
    • _controllersLock

      private final ReadWriteLock _controllersLock
      Lock for the controller list.
    • _controllersLoaded

      private boolean _controllersLoaded
      True once the configs were read.
    • _configFile

      private final String _configFile
      File holding the tunnel definitions.
    • _configDirectory

      private final String _configDirectory
      Directory of the per-tunnel configs.
    • REGISTERED_NAME

      private static final String REGISTERED_NAME
      Service name for ClientApp registration.
      See Also:
    • delayedShutdownInProgress

      private static volatile boolean delayedShutdownInProgress
      Flag to indicate if delayed server tunnel shutdown is in progress. Set by shutdownDelayedServers() and checked by GracefulShutdown.
    • delayedShutdownStartTime

      private static volatile long delayedShutdownStartTime
      Timestamp when delayed shutdown started, used to calculate remaining wait time.
    • cancelDelayedShutdown

      private static volatile boolean cancelDelayedShutdown
      Flag to signal that delayed shutdown should be cancelled. Set by cancelDelayedShutdown() and checked by shutdown tasks.
    • _delayedShutdownExecutor

      private ExecutorService _delayedShutdownExecutor
      Executor service for delayed shutdown tasks, used for cancellation.
    • _sessions

      private final Map<I2PSession, Set<TunnelController>> _sessions
      Session ownership map for preventing premature session close.
    • _executor

      private volatile ThreadPoolExecutor _executor
      Pool of socket handlers for all clients; volatile so the per-tunnel and per-request read path (getClientExecutor()) never takes _executorLock in steady state. Mutation (create/resize/kill) stays under the lock.
    • _executorThreadCount

      private static final AtomicLong _executorThreadCount
      Client handler threads created.
    • _executorLock

      private final Object _executorLock
      Lock for the client thread pool.
    • HANDLER_KEEPALIVE_MS

      private static final long HANDLER_KEEPALIVE_MS
      How long to wait before dropping an idle thread.
      See Also:
    • _serverHandlers

      Private handler pools for server tunnels, keyed by the server instance. Each server tunnel owns its pool so a saturated destination (its threads and its queue full) rejects only ITS excess connections; a flood on one port can no longer consume threads that other ports need.
    • _serverExecutorThreadCount

      private static final AtomicLong _serverExecutorThreadCount
      Server handler threads created.
    • _serverExecutorLock

      private final Object _serverExecutorLock
      Lock for the server thread pool.
    • _serverHandlerStatsRegistered

      private boolean _serverHandlerStatsRegistered
      True once the server pool stats are registered.
    • SERVER_KEEPALIVE_MS

      private static final long SERVER_KEEPALIVE_MS
      Server handler idle keepalive.
      See Also:
    • SERVER_HANDLER_MAX_THREADS

      static final int SERVER_HANDLER_MAX_THREADS
      Heap-safety ceiling on summed server handler threads (Tuner param, heap-clamped in the Tuner).
      See Also:
    • SERVER_HANDLER_PER_TUNNEL_MAX

      static final int SERVER_HANDLER_PER_TUNNEL_MAX
      Per-tunnel server-handler cap ceiling (Tuner per-tunnel param max).
      See Also:
    • SERVER_HANDLER_FLOOR

      static final int SERVER_HANDLER_FLOOR
      Absolute floor for a live server tunnel's handler pool.
      See Also:
    • SERVER_HANDLER_IDLE

      static final int SERVER_HANDLER_IDLE
      Worker threads kept for a server tunnel that is currently serving nothing. Deliberately 1, not SERVER_HANDLER_FLOOR: a fixed floor is charged against every open server tunnel before any demand weighting is applied, so on a router with many eepsites it consumed almost the entire budget and left the busiest tunnel with a handful of threads while it rejected connections. An idle tunnel costs one thread and grows on its own demand.
      See Also:
    • SERVER_HANDLER_HEADROOM

      static final int SERVER_HANDLER_HEADROOM
      Extra threads a saturated pool is granted beyond its current load, so a pool that is exactly full can still accept the connection that is being admitted right now instead of rejecting it and then growing.
      See Also:
    • _runnerPools

      Private runner pools for client and server tunnels, keyed by tunnel identity (I2PTunnel or I2PTunnelServer). Each pool is a share of the global clientRunnerMax budget so a saturated dest/tunnel cannot consume every runner thread. Rejection is per-tunnel (AbortPolicy after the short burst queue fills) rather than global.
    • _runnerPoolThreadCount

      private static final AtomicLong _runnerPoolThreadCount
      Runner threads created.
    • _runnerPoolLock

      private final Object _runnerPoolLock
      Lock for the runner thread pool.
    • _runnerPoolStatsRegistered

      private boolean _runnerPoolStatsRegistered
      True once the runner pool stats are registered.
    • RUNNER_POOL_FLOOR

      static final int RUNNER_POOL_FLOOR
      Absolute floor for a live tunnel's runner pool (shares of clientRunnerMax).
      See Also:
    • RUNNER_BURST_QUEUE

      static final int RUNNER_BURST_QUEUE
      Micro-burst absorption: tasks wait here when every worker is busy before AbortPolicy. Small enough that a stuck flood still rejects promptly (and sheds with a real HTTP page), large enough to cover a browser's parallel open (typically ~6) plus a short spike.
      See Also:
    • serverHandlerThreads

      private static volatile int serverHandlerThreads
      Tuned by Tuner: heap-safety ceiling on summed server handler threads across all server tunnels.
    • clientRunnerMax

      private static volatile int clientRunnerMax
      Tuned by Tuner.
    • serverBacklogQueueCapacity

      private static volatile int serverBacklogQueueCapacity
      Tuned by Tuner: how many inbound connections may wait in the server handler queue.
    • serverThreadsPerTunnel

      private static volatile int serverThreadsPerTunnel
      Tuned by Tuner: default per-tunnel ceiling on server handler threads.
    • CLIENT_RUNNER_MAX_MAX

      private static final int CLIENT_RUNNER_MAX_MAX
      Ceiling for the concurrent client worker pool.
      See Also:
    • CLIENT_MAX_CONNECTIONS_MIN

      private static final int CLIENT_MAX_CONNECTIONS_MIN
      Low clamp for the Tuner-managed default client cap.
      See Also:
    • CLIENT_MAX_CONNECTIONS_MAX

      private static final int CLIENT_MAX_CONNECTIONS_MAX
      High clamp for the Tuner-managed default client cap.
      See Also:
    • clientDefaultMaxConnections

      private static volatile int clientDefaultMaxConnections
      The Tuner-managed default concurrent-connection cap for client tunnels that do not configure an explicit "i2ptunnel.maxConnections" override. Kept separate from clientRunnerMax (the shared HTTP proxy executor ceiling) because that cap bounds the worker pool, whereas this cap gates the accept loop from over-dispatching before a handler thread exists.

      An explicit per-tunnel override always wins over this default; the Tuner can only move the floor for tunnels that leave the cap at default. Raised under sustained load to defer connection shedding (which the browser reports as an empty response), lowered under idle to conserve memory/FDs.

    • socketConnectTimeout

      private static volatile int socketConnectTimeout
      Socket connect timeout in ms, tuned by Tuner.
    • RATES

      static final long[] RATES
      Rate stat intervals.
    • ILLEGAL

      private static final char[] ILLEGAL
      From i2psnark Storage.java.
    • RUNNER_ELEVATION_LOG_INTERVAL_MS

      private static final long RUNNER_ELEVATION_LOG_INTERVAL_MS
      Minimum interval between elevation warnings for one tunnel.
      See Also:
    • MAX_TRACKED_ELEVATION_TUNNELS

      private static final int MAX_TRACKED_ELEVATION_TUNNELS
      Bound on tracked tunnels for _lastElevationLog, cleared wholesale when reached. The map is keyed by tunnel instance, which is bounded by the number of tunnels this router runs, so the cap only guards against a leak from tunnels created and destroyed repeatedly.
      See Also:
    • _lastElevationLog

      private static final Map<Object,Long> _lastElevationLog
      Last elevation warning per tunnel, for shouldLogElevation(Object, long).
    • REBALANCE_THROTTLE_MS

      private static final long REBALANCE_THROTTLE_MS
      Minimum interval between rebalances triggered from the accept hot path.
      See Also:
    • _lastRebalanceAt

      private static volatile long _lastRebalanceAt
      Last time a hot-path rebalance was run.
  • Constructor Details

    • TunnelControllerGroup

      public TunnelControllerGroup(I2PAppContext context, ClientAppManager mgr, String[] args)
      Instantiation only. Caller must call startup(). Config file problems will not throw exception until startup().
      Parameters:
      context - the context the tunnels are created in
      mgr - may be null
      args - zero or one args, which may be one config file or one config directory. If not absolute will be relative to the context's config dir, if empty or null, the default is i2ptunnel.config for a config file and i2ptunnel.config.d for a config directory
      Throws:
      IllegalArgumentException - if too many args
  • Method Details

    • getServerHandlerThreads

      public static int getServerHandlerThreads()
      The number of server handler threads available in total across all server tunnels: the heap-safety ceiling for the per-tunnel handler pools.
      Returns:
      the global server handler thread budget
    • setServerHandlerThreads

      public static void setServerHandlerThreads(int val)
      Clamp and set the heap-safety ceiling on summed server handler threads, 2 to 16384. This is not a budget the pools divide: each pool is sized from its own inbound demand by serverThreadsForDemand(int, int, int) and capped by its per-tunnel ceiling (getServerThreadsPerTunnel()), and this value only trims every pool proportionally via scaleDemandsToCeiling(int[], int) when the total is unsupportable. The high ceiling lets the Tuner keep pace with sustained inbound load where workers stall on slow inbound I2P writes rather than finishing quickly; the memory-derived clamp in the Tuner keeps the reachable ceiling at what the max heap can host. Live pools are rebalanced to the new ceiling.
      Parameters:
      val - the desired ceiling
    • getServerBacklogQueueCapacity

      public static int getServerBacklogQueueCapacity()
      The maximum number of connections that may wait in the server handler bounded queue before overflow rejection (ThreadPoolExecutor.AbortPolicy).
      Returns:
      the queue capacity
    • setServerBacklogQueueCapacity

      public static void setServerBacklogQueueCapacity(int val)
      Clamp and set the server handler queue capacity, 16 to 65536. The Tuner I2PTunnelServerBacklogParam stays within this same range.
      Parameters:
      val - the desired capacity
    • getIOTransferThreads

      public static int getIOTransferThreads()
      I/O transfer pool size for Server→Client data forwarding. Delegates to I2PTunnelHTTPServer.
      Returns:
      the current I/O transfer thread count
    • setIOTransferThreads

      public static void setIOTransferThreads(int val)
      Set the I/O transfer pool size (clamped to [2, 64]).
      Parameters:
      val - desired pool size
    • getIOTransferActiveCount

      public static int getIOTransferActiveCount()
      Active thread count in the I/O transfer pool (live saturation signal).
      Returns:
      active threads, or 0 if pool not yet created
    • getIOMaxPoolUtilization

      public static double getIOMaxPoolUtilization()
      The busiest per-tunnel I/O transfer pool's utilization: its active plus queued transfers divided by its present maximum, as a rate that may exceed 1.0 when the queue backs up past the pool's ceiling. Delegates to I2PTunnelHTTPServer.

      Per-pool, not a global sum — the tuner grows the shared budget from this so one saturated pool stays visible however quiet the others are.

      Returns:
      the busiest pool's utilization rate, or 0.0 when no pools exist
    • getIOStallTimeoutMs

      public static long getIOStallTimeoutMs()
      I/O stall timeout in ms for Server→Client transfers.
      Returns:
      the current stall timeout
    • setIOStallTimeoutMs

      public static void setIOStallTimeoutMs(long val)
      Set the I/O stall timeout in ms (clamped to [5000, 300000]).
      Parameters:
      val - desired timeout in ms
    • getStallEventCount

      public static long getStallEventCount()
      Total stall events detected since router start (monotonically increasing). Used by the Tuner as a real-time signal for stall timeout adjustment.
      Returns:
      total stall event count
    • getLiveServerHandlerQueueDepth

      public static int getLiveServerHandlerQueueDepth()
      Live aggregate queue depth across all server tunnel executor pools. Reads directly from each ThreadPoolExecutor.getQueue()'s size, bypassing the Rate-averaged stat for instant saturation detection.
      Returns:
      total queued tasks across all server handler pools, or 0 if none
    • getLiveServerHandlerActiveCount

      public static int getLiveServerHandlerActiveCount()
      Live aggregate active thread count across all server tunnel executor pools. Reads directly from each ThreadPoolExecutor.getActiveCount(), bypassing the Rate-averaged stat for instant saturation detection.
      Returns:
      total active threads across all server handler pools, or 0 if none
    • getLiveServerHandlerMaxThreads

      public static int getLiveServerHandlerMaxThreads()
      Live aggregate max pool size across all server tunnel executor pools. The sum of each pool's ThreadPoolExecutor.getMaximumPoolSize(), representing total handler capacity.
      Returns:
      total max threads across all server handler pools, or 0 if none
    • getServerThreadsPerTunnel

      public static int getServerThreadsPerTunnel()
      The default per-tunnel ceiling on server handler threads (the private handler-pool size for a server tunnel that does not set an explicit i2ptunnel.server.threads override). The pool for any server tunnel is at most this many threads, bounded in total by the global budget (getServerHandlerThreads()).
      Returns:
      the default per-tunnel cap
    • setServerThreadsPerTunnel

      public static void setServerThreadsPerTunnel(int val)
      Clamp and set the default per-tunnel ceiling on server handler threads, 2 to 4096. The Tuner I2PTunnelServerThreadsParam stays within this range.
      Parameters:
      val - the desired default per-tunnel cap
    • getClientRunnerMax

      public static int getClientRunnerMax()
      The maximum number of concurrent client connections.
      Returns:
      the client runner max
    • setClientRunnerMax

      public static void setClientRunnerMax(int val)
      Clamp and set the maximum concurrent client connections, 4 to 16384. The Tuner I2PTunnelClientRunnerMaxParam stays within this same range.

      Enforces a one-way invariant against the Tuner-managed admission gate (clientDefaultMaxConnections): the worker pool may never be shrunk below what the gate admits. Connections are served thread-per-connection with only a short RUNNER_BURST_QUEUE of slack, so every admitted connection needs its own worker; if the runner were allowed to collapse below the gate, bursts would overflow the worker pool and be closed with zero bytes as empty responses. Flooring the runner at the gate keeps admission and serving capacity in the same ballpark; the runner may still grow above the gate to absorb bursts.

      Parameters:
      val - the desired maximum
    • getClientDefaultMaxConnections

      public static int getClientDefaultMaxConnections()
      The Tuner-managed default concurrent-connection cap for unset client tunnels.
      Returns:
      the default caps, 256 to 16384
    • setClientDefaultMaxConnections

      public static void setClientDefaultMaxConnections(int val)
      Clamp and set the Tuner-managed default concurrent-connection cap, 256 to 16384. The Tuner I2PTunnelClientMaxConnectionsParam stays within this range.
      Parameters:
      val - the desired default cap
    • getSocketConnectTimeout

      public static int getSocketConnectTimeout()
      The socket connect timeout in milliseconds.
      Returns:
      the socket connect timeout
    • setSocketConnectTimeout

      public static void setSocketConnectTimeout(int val)
      Clamp and set the socket connect timeout, 5000 to 120000 ms.
      Parameters:
      val - the timeout in milliseconds
    • getInstance

      public static TunnelControllerGroup getInstance()
      In I2PAppContext will instantiate if necessary and always return non-null. As of 0.9.4, when in RouterContext, will return null if the TCG has not yet been started by the router. As of 0.9.41, that's true for Android as well.

      In Android, this should be used for all calls except from LoadClientsJob, as we do not want to instantiate TCG too early. Android must do null checks on the return value.

      Returns:
      the shared group, or null in RouterContext before the router starts it
      Throws:
      IllegalArgumentException - if unable to load from i2ptunnel.config
    • getInstance

      public static TunnelControllerGroup getInstance(I2PAppContext ctx)
      In I2PAppContext will instantiate if necessary and always return non-null. When in RouterContext, will return null (except in Android) if the TCG has not yet been started by the router. In Android, if the old instance uses a stale context, it will replace it.

      In Android, this should only be called from LoadClientsJob, as we do not want to instantiate TCG too early.

      Parameters:
      ctx - the context the instance is built against, replacing a stale Android one
      Returns:
      the shared group, or null in RouterContext before the router starts it
      Throws:
      IllegalArgumentException - if unable to load from i2ptunnel.config
    • setupArguments

      private String[] setupArguments(String[] args)
      Reads argument list and returns two strings that can be used to instantiate _configFile and _configDirectory. After calling this method, the returned Strings must be assigned to _configFile and _configDirectory.
      Parameters:
      args - must be the args passed to TunnelControllerGroup.
      Returns:
      an array of exactly 2 strings, where [0] is the the value for _configFile and [1] is the value for _configDirectory
    • resolveConfig

      private File resolveConfig(String name)
      Resolve a config path to an absolute file under the config dir.
      Parameters:
      name - the config file or directory path
      Returns:
      the resolved file
    • main

      public static void main(String[] args)
      Load the tunnel configs from a file, for running standalone.
      Parameters:
      args - one arg, the config file, if not absolute will be relative to the context's config dir, if no args, the default is i2ptunnel.config
      Throws:
      IllegalArgumentException - if unable to load from config from file
    • getContext

      public I2PAppContext getContext()
      Retrieves the application context the group was created with.
      Returns:
      the context this group was built against
    • startup

      public void startup()
      ClientApp interface.
      Specified by:
      startup in interface ClientApp
      Throws:
      IllegalArgumentException - if unable to load config from file
    • getState

      public ClientAppState getState()
      ClientApp interface.
      Specified by:
      getState in interface ClientApp
      Returns:
      the current client app state
    • getName

      public String getName()
      The registered name of this tunnel group, for the ClientApp interface.
      Specified by:
      getName in interface ClientApp
      Returns:
      the constant "i2ptunnel" under which this group is registered with the ClientApp manager, distinct from the display name
    • getDisplayName

      public String getDisplayName()
      The display name of this tunnel group, for the ClientApp interface.
      Specified by:
      getDisplayName in interface ClientApp
      Returns:
      the display name
    • changeState

      private void changeState(ClientAppState state)
      Change the client app state.
      Parameters:
      state - the new state
    • changeState

      private void changeState(ClientAppState state, Exception e)
      Change the client app state and notify the manager.
      Parameters:
      state - the new state
      e - cause of the change, null if there is none
    • shutdown

      public void shutdown(String[] args)
      ClientApp interface.
      Specified by:
      shutdown in interface ClientApp
      Parameters:
      args - generally null but could be stopArgs from clients.config
    • shutdown

      public void shutdown(boolean waitForDelayed)
      Warning - destroys the singleton! Caller must root a new context before calling instance() or main(String[]) again. Aggressively kill and null everything to reduce memory usage in the JVM after stopping, and to recognize what must be reinitialized on restart (Android).
      Parameters:
      waitForDelayed - true to wait for shutdown-delayed tunnels, false for immediate
    • shutdown

      public void shutdown()
      Convenience method - calls shutdown(true) to wait for.
    • prepareGracefulShutdown

      public void prepareGracefulShutdown()
      Prepare for graceful shutdown by stopping delayed server tunnels. This should be called during graceful shutdown period, before final shutdown is triggered. It stops server tunnels with delays but does not unload controllers or release resources.
    • isDelayedShutdownInProgress

      public static boolean isDelayedShutdownInProgress()
      Check if delayed server tunnel shutdown is currently in progress.
      Returns:
      true if shutdownDelayedServers() is currently executing
    • getMaxShutdownDelay

      public int getMaxShutdownDelay()
      Get the maximum configured shutdown delay among all server tunnels.
      Returns:
      maximum delay in seconds, or 0 if no servers have delays configured
    • getRemainingShutdownDelay

      public static int getRemainingShutdownDelay()
      Get the remaining time until the longest-delayed server tunnel will stop.
      Returns:
      remaining delay in seconds, or 0 if shutdown not in progress or delay has passed
    • cancelDelayedShutdown

      public void cancelDelayedShutdown()
      Cancel a delayed server tunnel shutdown and restart any servers that were stopped. This should be called when the user cancels a graceful router shutdown/restart.
    • shutdownDelayedServers

      private void shutdownDelayedServers()
      Stop server tunnels with shutdown delays independently. Each server with a delay gets its own timer and stops independently.

      synchronized so concurrent invocations (e.g. a cancel racing a repeated graceful-shutdown) cannot both create and tear down the shared _delayedShutdownExecutor; otherwise one thread's cleanup can null the field while another is still dereferencing it.

    • getDelayedServers

      private List<TunnelController> getDelayedServers()
      Collect the running server tunnels that have a shutdown delay.
      Returns:
      the delayed servers, empty if none
    • scheduleDelayedShutdowns

      private void scheduleDelayedShutdowns(List<TunnelController> delayedServers, List<TunnelController> stoppedServers)
      Submit one shutdown task per delayed server, each waiting a random delay in the configured range before stopping its tunnel.
      Parameters:
      delayedServers - the servers to stop
      stoppedServers - the list to record the stopped servers in
    • cleanupShutdown

      private void cleanupShutdown(List<TunnelController> stoppedServers)
      Stop the shutdown executor, restarting any servers that were stopped if the shutdown was cancelled.
      Parameters:
      stoppedServers - the servers stopped by the shutdown tasks
    • shouldMigrate

      private boolean shouldMigrate()
      Detects whether a migration to split configuration files should/will/has happened based on the platform and installation type. Does not tell whether a migration has actually occurred.
      Returns:
      true if a migration is relevant to the platform, false if not
    • loadControllers

      public void loadControllers(File cfgFile)
      Load up all of the tunnels configured in the given file. Prior to 0.9.20, also started the tunnels. As of 0.9.20, does not start the tunnels, you must call startup() or getInstance() instead of loadControllers().

      DEPRECATED for use outside this class. Use startup() or getInstance().

      Parameters:
      cfgFile - the config file or directory to read the tunnel definitions from
      Throws:
      IllegalArgumentException - if unable to load from file
    • loadControllers

      private void loadControllers(File cfgFile, boolean shouldMigrate)
      Load the controllers from the config file.
      Parameters:
      cfgFile - the i2ptunnel config file to read
      shouldMigrate - migrate to, and load from, i2ptunnel.config.d
      Throws:
      IllegalArgumentException - if unable to load from file
    • migrate

      private boolean migrate(List<Properties> tunnels, File from, File dir)
      Copies old tunnel configs into the new format.
      Parameters:
      tunnels - the tunnel configs to migrate
      from - the directory of the old configs
      dir - the directory to write the new configs to
      Returns:
      true if any config was migrated
    • startControllers

      private void startControllers()
      Start all of the tunnels. Must call loadControllers(File) first.
    • reloadControllers

      public void reloadControllers()
      Stop all tunnels, reload config, and restart those configured to do so. WARNING - Does NOT simply reload the configuration!!! This is probably not what you want. This does not return or clear the controller messages.
      Throws:
      IllegalArgumentException - if unable to reload config file
    • unloadControllers

      public void unloadControllers()
      Stop and remove reference to all known tunnels (but don't delete any config file or do other silly things).
    • addController

      public void addController(TunnelController controller)
      Add the given tunnel to the set of known controllers (but don't add it to a config file or start it or anything).
      Parameters:
      controller - the tunnel to add to the known set
    • removeController

      public List<String> removeController(TunnelController controller)
      Stop and remove the given tunnel. Side effect - clears all messages the controller. Does NOT delete the configuration - must call saveConfig() or removeConfig() also.
      Parameters:
      controller - the tunnel to stop and drop, where null yields an empty message list
      Returns:
      list of messages from the controller as it is stopped
    • stopAllControllers

      public List<String> stopAllControllers()
      Stop all tunnels. May be restarted. Side effect - clears all messages from all controllers.
      Returns:
      list of messages the tunnels generate when stopped
    • destroyAllControllers

      private void destroyAllControllers()
      Stop all tunnels. They may not be restarted, you must reload. Caller must synch. Caller must clear controller list.
    • startAllControllers

      public List<String> startAllControllers()
      Start all tunnels. Side effect - clears all messages from all controllers.
      Returns:
      list of messages the tunnels generate when started
    • restartAllControllers

      public List<String> restartAllControllers()
      Restart all tunnels. Side effect - clears all messages from all controllers.
      Returns:
      list of messages the tunnels generate when restarted
    • clearAllMessages

      public List<String> clearAllMessages()
      Fetch and clear all outstanding messages from any of the known tunnels.
      Returns:
      list of messages the tunnels have generated
    • saveConfig

      private void saveConfig(File cfgFile) throws IOException
      Save the configuration of all known tunnels to the given file. Side effect: for split config, sets "confFile" property to absolute path.
      Parameters:
      cfgFile - the file to write all tunnel configs to
      Throws:
      IOException - if the file cannot be created or written
    • saveConfig

      public void saveConfig(TunnelController tc) throws IOException
      Save the configuration of this tunnel only, may be new. Side effect: for split config, sets "confFile" property to absolute path.
      Parameters:
      tc - the controller whose configuration is saved
      Throws:
      IOException - if the config file cannot be written
    • removeConfig

      public void removeConfig(TunnelController tc) throws IOException
      Remove the configuration of this tunnel only.
      Parameters:
      tc - the controller whose config file is renamed to a .bak and deleted
      Throws:
      IOException - if the config file cannot be renamed
    • sanitize

      private static String sanitize(String rv)
      Replace problematic characters in file name.
      Parameters:
      rv - the proposed file name, replaced characters become '_'
      Returns:
      the name with every illegal character replaced by '_'
    • assureConfigFile

      private File assureConfigFile(TunnelController tc)
      Return the config File associated with a TunnelController or a default File based on the tunnel name.
      Parameters:
      tc - the controller whose config file is wanted
      Returns:
      the File ready for use
    • listFiles

      private List<File> listFiles()
      List all the config files in the config directory, or the monolithic config file, if they exist.
      Returns:
      non-null list of files, sorted
    • loadConfig

      private List<Properties> loadConfig(File cfgFile) throws IOException
      Load up the config data from either type of config file automatically. Side effect: for split config, sets "confFile" property to absolute path.
      Parameters:
      cfgFile - the monolithic or split config file to read
      Returns:
      non-null, properties loaded, one for each tunnel
      Throws:
      IOException - if unable to load from file
    • splitMonolithicConfig

      private List<Properties> splitMonolithicConfig(Properties config)
      Split up the config data loaded from a single file, this is the old version for the numbered config file, into properties one for each tunnel.
      Parameters:
      config - the monolithic properties with numbered tunnel key prefixes
      Returns:
      non-null, properties loaded, one for each tunnel
    • getControllers

      public List<TunnelController> getControllers()
      Retrieve a list of tunnels known.

      Side effect: if the tunnels have not been loaded from config yet, they will be.

      Returns:
      list of TunnelController objects
      Throws:
      IllegalArgumentException - if unable to load config from file
    • getControllers

      private List<TunnelController> getControllers(File cfgFile)
      Retrieve a list of tunnels known.

      Side effect: if the tunnels have not been loaded from config yet, they will be.

      Parameters:
      cfgFile - the config file to load from if not loaded yet
      Returns:
      list of TunnelController objects
      Throws:
      IllegalArgumentException - if unable to load config from file
    • acquire

      void acquire(TunnelController controller, I2PSession session)
      Note the fact that the controller is using the session so that it isn't destroyed prematurely.
      Parameters:
      controller - the controller claiming a reference to the session
      session - the session being claimed
    • release

      void release(TunnelController controller, I2PSession session)
      Note the fact that the controller is no longer using the session, and if no other controllers are using it, destroy the session.
      Parameters:
      controller - the controller giving up its reference to the session
      session - the session being released
    • getClientExecutor

      ThreadPoolExecutor getClientExecutor()
      The executor pool for client tunnel tasks.

      Read path is lock-free in steady state (_executor is volatile): the common case is a null-and-no-change return with no synchronized acquisition, so per-request callers (I2PTunnelHTTPClientRunner) are not serialized on the global _executorLock and the Tuner load sample is not written inside a monitor. Create/resize races resolve under the lock with a double-check.

      Returns:
      non-null
    • getServerExecutor

      ThreadPoolExecutor getServerExecutor(I2PTunnelServer server, int threadOverride)
      Get (creating and sizing on first use) the private handler pool for a server tunnel. All pools are sized together so their sum never exceeds the Tuner-managed global budget while each pool respects its per-tunnel ceiling. Registration records the tunnel's explicit i2ptunnel.server.threads cap (or -1 to follow the Tuner-managed default), rebalancing every live pool to make room for the newcomer.

      Tasks are short-lived (µs-scale), so core threads handle bursts via a bounded queue. Overflow throws RejectedExecutionException (AbortPolicy) rather than running the handler inline on the accept thread, so the accept loop can never be pinned by a write-blocked handler, and the rejection is per-port: only the saturated tunnel's own connections are dropped.

      Parameters:
      server - the server tunnel requesting a pool; never null
      threadOverride - the per-tunnel thread cap (>=2), or -1 for default
      Returns:
      non-null, the server's private executor
    • refreshServerThreadOverride

      void refreshServerThreadOverride(I2PTunnelServer server, int threadOverride)
      Recompute and apply a server tunnel's per-tunnel thread cap after a live config change (optionsUpdated), resizing the private pool when the override differs from what was registered.
      Parameters:
      server - the running server tunnel; ignored if not registered
      threadOverride - the new per-tunnel thread cap (>=2), or -1 for default
    • serverStopped

      void serverStopped(I2PTunnelServer server)
      Deregister a stopped server tunnel: shut down its private pool (queued tasks drain, then the worker threads exit) and redistribute its share of the global budget to the remaining tunnels.
      Parameters:
      server - the stopped server tunnel
    • getServerRunnerExecutor

      ThreadPoolExecutor getServerRunnerExecutor(I2PTunnelServer server, int ceiling)
      Get (creating and sizing on first use) the private runner pool for a server tunnel's connection handlers (HTTP runner, CompressedRequestor, IRC/DCC). Split from the client runner budget so inbound server load cannot starve client tunnels and vice versa. Each pool is a share of the global clientRunnerMax budget.
      Parameters:
      server - the server tunnel requesting a pool; never null
      ceiling - per-tunnel max-connections ceiling (>0), or <=0 for the default
      Returns:
      non-null, the server's private runner executor
    • getClientRunnerExecutor

      ThreadPoolExecutor getClientRunnerExecutor(I2PTunnel tunnel, int ceiling)
      Get (creating and sizing on first use) the private runner pool for a client tunnel. Ceiling is the tunnel's effective maxConnections so a flood on one dest cannot consume runners other tunnels need.
      Parameters:
      tunnel - the client tunnel requesting a pool; may be null (falls back to the global executor)
      ceiling - per-tunnel max-connections ceiling (>0), or <=0 for the default
      Returns:
      non-null, the tunnel's private runner executor (or the global fallback)
    • sampleRunnerActiveThreads

      static void sampleRunnerActiveThreads(ThreadPoolExecutor ex)
      Sample a runner pool's live active-thread count into the Tuner primary stat i2ptunnel.clientRunner.activeThreads. Per-tunnel pools never go through getClientExecutor(), so without this the Tuner only sees the unused shared fallback and cannot grow the budget under load. Safe to call from the accept hot path; no locks.
      Parameters:
      ex - the pool to sample; ignored if null or shut down
    • runnerStopped

      void runnerStopped(Object key)
      Deregister a stopped tunnel's runner pool: shut it down (queued burst tasks drain, then workers exit) and redistribute its share of the global budget to the remaining tunnels.
      Parameters:
      key - the stopped tunnel identity (I2PTunnel or I2PTunnelServer)
    • getRunnerExecutor

      private ThreadPoolExecutor getRunnerExecutor(Object key, int ceiling)
      Shared create-or-get for a per-tunnel runner pool. Must run under _runnerPoolLock. Rebalances every live pool so the sum of maxima never exceeds clientRunnerMax.
      Parameters:
      key - tunnel identity; never null
      ceiling - this tunnel's per-tunnel max-connections ceiling
      Returns:
      non-null executor
    • rebalanceRunnerPools

      private void rebalanceRunnerPools()
      Rebalance every live runner pool from the global clientRunnerMax budget and each tunnel's ceiling. Must run under _runnerPoolLock. The global budget is a cap on the sum of maxima, so reducing one tunnel's share or removing a tunnel frees budget for the rest.
    • ensureRunnerPoolStats

      private void ensureRunnerPoolStats()
      Register the per-tunnel runner pool rate stats exactly once.
    • rebalanceAllRunnerPools

      private void rebalanceAllRunnerPools()
      Trigger a full runner-pool rebalance after a global budget change.
    • rebalanceRunnerPoolsNow

      static void rebalanceRunnerPoolsNow()
      Trigger a load-aware runner-pool rebalance from the accept hot path after a RejectedExecutionException: an idle sibling may shrink its claim and free budget for this tunnel before the retry. Safe to call with no router instance (no-op).
    • claimRunnerShare

      static int claimRunnerShare(int cap, int queueDepth, int active)
      Load-aware runner-pool claim: same semantics as claimServerHandlerShare(int, int, int, int) with the runner floor. Idle pools claim half their ceiling; busy pools (queued or active work) claim the full ceiling so the proportional cut prefers them.
      Parameters:
      cap - this tunnel's runner ceiling
      queueDepth - queued tasks in this tunnel's runner pool
      active - busy runner threads in this tunnel's pool
      Returns:
      claimed threads in [RUNNER_POOL_FLOOR, cap]
    • runnerCeilingFor

      static int runnerCeilingFor(TunnelControllerGroup.RunnerHandler h)
      The runner ceiling for one pool, read now rather than remembered.
      Parameters:
      h - the pool's state; may be null
      Returns:
      the explicit cap, or the Tuner-managed default as it stands now, never below the per-tunnel floor
    • elevatedRunnerMax

      static int elevatedRunnerMax(int currentThreads, int ceiling)
      How many threads a pool pinned at current with work queued may be elevated to above its ceiling, and how often the elevation is logged.

      Both are deliberate: a bounded multiple keeps one tunnel from claiming an unbounded share of the memory budget, and a log interval keeps a tracker under sustained load from writing thousands of identical lines — the earlier saturation warning rotated a 100 MB log file every fifteen minutes.

      Parameters:
      currentThreads - the pool's present maximum
      ceiling - the live ceiling for this tunnel
      Returns:
      the elevated maximum, or currentThreads when no elevation applies
    • shouldLogElevation

      boolean shouldLogElevation(Object key, long now)
      Whether to log an elevation for this tunnel now. Rate-limited per tunnel, so a sustained burst is reported once per interval rather than once per request.
      Parameters:
      key - the tunnel identity used as the map key
      now - current time in ms
      Returns:
      true when the caller should log at WARN
    • shouldReleaseElevation

      static boolean shouldReleaseElevation(long completed, long idleSince, int elevatedMax)
      Whether an elevation has gone stale and should be released.

      Pure, so the decay rule can be pinned without a group or a live pool. The rule: a pool elevated to elevatedMax gives the shared budget back once it has completed that many tasks since demand last stopped. Work rather than seconds, because the tunnels that get elevated are the ones that accumulate idle observations quickly — a wall-clock interval would be far too generous for a tracker idling between announce batches and far too tight for a browser idling between page loads.

      Parameters:
      completed - the pool's completed-task count now
      idleSince - the completed count when demand last stopped, or 0 if none
      elevatedMax - the pool's present floor, or 0 if it was never elevated
      Returns:
      true when the floor should be released
    • shouldElevateRunner

      static boolean shouldElevateRunner(int queueDepth, int currentThreads, int ceiling)
      Whether a pinned pool with this backlog should be elevated past its ceiling.

      Pure, so the trigger can be pinned without a group or a live pool. It requires the queue to be at least half of RUNNER_BURST_QUEUE: a submit that finds every worker busy plus one task in flight looks identical to real backlog at that instant, and elevating on that race produces churn — measured live as 256->384 three times in twenty minutes on a tracker, with the decay clawing it back each time.

      Parameters:
      queueDepth - queued tasks in the pool
      currentThreads - the pool's present maximum
      ceiling - the live ceiling for this tunnel
      Returns:
      true when the backlog justifies elevating past the ceiling
    • noteRunnerIdle

      void noteRunnerIdle(Object key)
      Note that a submit found this pool quiet, and drop a stale elevation if the tunnel has done enough work while quiet to say the burst is over.

      The rule is expressed in work, not seconds: the floor is released once the pool has completed at least elevatedMax tasks since demand last stopped. A wall-clock interval would be arbitrary for a tracker that idles for a second between announce batches and for a browser that idles for a minute between page loads; counting completed work is self-scaling, because the tunnels that get elevated are the ones that can accumulate idle observations quickly. Any further demand through growRunnerExecutorForLoad(Object, int, int) clears the baseline, so a burst with short lulls keeps its headroom.

      This is why the method is separate from the grow: the caller's fast path is one atomic queue read, and only a pool that was actually elevated pays for the decay check.

      Parameters:
      key - the runner-pool key, which is the accepting I2PTunnelServer
    • growRunnerExecutorForLoad

      int growRunnerExecutorForLoad(Object key, int queueDepth, int active)
      Grow one server tunnel's runner pool to match the load it is actually seeing, elevating past the ceiling and warning when demand warrants.

      Called immediately before a runner submit rather than from the accept loop: the accept loop owns the server handler pool, and a grow there would rebalance on connections that never reach the runner at all. Growing here means the pool is sized at the moment the work is handed to it, and the ceiling is read live, so it also picks up a raised Tuner default.

      Grows only — this method never shrinks. Releasing an elevation once the tunnel has gone quiet is noteRunnerIdle(Object)'s job, and for a different reason: shrinking on a momentary lull would thrash the limit, whereas allowCoreThreadTimeOut(true) means a generous maximum costs no threads while the pool is idle, its workers being reclaimed either way.

      Parameters:
      key - the runner-pool key, which is the accepting I2PTunnelServer
      queueDepth - queued tasks in the pool
      active - busy runners in the pool
      Returns:
      the pool's maximum after any growth, or 0 when there is no pool yet
    • createRunnerExecutor

      static ThreadPoolExecutor createRunnerExecutor(int maxThreads, AtomicLong index)
      Create a thread-per-connection runner pool for one tunnel. Core == max so concurrent admits grow workers up to the ceiling before anything queues; a short RUNNER_BURST_QUEUE absorbs a micro-burst (browser parallel opens) instead of rejecting to a shed page; overflow still surfaces as RejectedExecutionException to that tunnel's accept path only. Idle threads are reclaimed after the keepalive window via allowCoreThreadTimeOut.

      Core must equal max when a real queue is present: with core 0 the ThreadPoolExecutor offers to the queue before creating any worker, so tasks would sit unrun until the queue filled.

      Parameters:
      maxThreads - the fixed max worker count for this tunnel's pool
      index - an AtomicLong counter used to name worker threads
      Returns:
      a never-null executor
    • resizeRunnerExecutor

      private static void resizeRunnerExecutor(ThreadPoolExecutor ex, int newMax)
      Resize a per-tunnel runner pool, keeping core == max so the burst queue is only reached after every worker is busy.
      Parameters:
      ex - the pool; ignored if null or shut down
      newMax - the new max pool size
    • rebalanceServerExecutors

      private void rebalanceServerExecutors()
      Rebalance every live server-tunnel pool from the global budget, the per-tunnel caps, and the per-tunnel floor. Called on budget/cap changes and on server start/stop; must run under _serverExecutorLock. The global budget is a cap on the sum, so reducing one tunnel's share or removing a tunnel frees budget for the rest.

      Claims are load-aware: a tunnel with queued or active work claims its full ceiling, while an idle tunnel claims only a base share so a busy sibling (e.g. a tracker under announce flood) receives the budget when the sum of ceilings exceeds the global cap. Under a sufficient budget every tunnel still receives its full ceiling.

    • claimServerHandlerShare

      static int claimServerHandlerShare(int cap, int floor, int queueDepth, int active)
      Load-aware claim for one server tunnel's share of the global handler budget. Pure decision, no router context.

      When the sum of per-tunnel ceilings exceeds the budget, proportional cutting of equal full-cap claims starves the busy tunnel just as much as an idle one. Scaling the claim by observed load gives queued/active work the threads it needs while idle tunnels keep only a base share (still at or above the floor once allocateServerThreads(int, int[], int) applies).

      Parameters:
      cap - this tunnel's ceiling (per-tunnel override or default)
      floor - shared per-tunnel floor
      queueDepth - queued tasks in this tunnel's pool
      active - busy handler threads in this tunnel's pool
      Returns:
      claimed threads in [floor, cap]
    • growServerExecutorForLoad

      void growServerExecutorForLoad(I2PTunnelServer server)
      Grow one server's handler pool to match the load it is actually seeing.

      Called from the accept loop before the admission gate, because that is the only place queue depth is visible at the moment it matters. rebalanceServerExecutors() runs on pool registration and on Tuner parameter changes, not on load, so a pool registered while idle would otherwise stay at its idle size forever. The first version of demand sizing had exactly that: pools created at one thread, refusing connections with an almost-empty queue, because nothing ever asked them to grow.

      Grows only. Shrinking here would thrash the limit on every lull and buy nothing — allowCoreThreadTimeOut(true) means a generous limit costs no threads while a pool is quiet, since its workers are reclaimed after the keepalive either way.

      Parameters:
      server - the accepting server tunnel
    • rebalanceAll

      private void rebalanceAll()
      Trigger a full handler-pool rebalance after a global or per-tunnel cap change.
    • normalizeThreadOverride

      static int normalizeThreadOverride(int v)
      The per-tunnel thread cap as used by getServerExecutor(I2PTunnelServer, int) and rebalanceServerExecutors(); a value outside the valid cap range means "follow the Tuner-managed default".
      Parameters:
      v - the raw per-tunnel override, or -1 for none
      Returns:
      v if it is a valid cap in [4, 16384], else -1
    • allocateServerThreads

      static int[] allocateServerThreads(int budget, int[] desired, int floor)
      Allocate a global server-handler thread budget across the live server tunnels, preserving each tunnel's ceiling (desired) and a shared floor. Pure decision, no router context: lets the Tuner shrink or grow one budget while the per-tunnel pools inherit exactly their share.

      When the ceilings sum at or below the budget, every tunnel gets its ceiling. Above the budget, the excess is cut proportionally (rounded down, remainder to the last tunnel) so the grand total equals the budget exactly and no tunnel falls below the floor — a genuine per-tunnel cap rather than a free-for-all for the aggregate. When the budget cannot cover a floor for every tunnel, the budget is split evenly with at least 1 thread per live tunnel.

      Parameters:
      budget - the global budget; <= 0 yields all zeros
      desired - per-tunnel ceilings (any values; clamped internally)
      floor - the per-tunnel floor; negative treated as 0
      Returns:
      per-tunnel allocation, same length as desired; each entry >= the shared floor whenever the budget allows; the sum equals min(budget, sum of ceilings) except when the budget is smaller than one thread per tunnel
    • serverThreadsForDemand

      static int serverThreadsForDemand(int queueDepth, int active, int cap)
      Worker threads one server tunnel needs, from its own demand alone.

      Each server tunnel is sized independently. There is no global budget to divide, because dividing one is what made the busiest eepsite unusable: every open server tunnel was charged a floor first, so with twenty eepsites the floor consumed the budget and the pool actually serving requests was left with a ceiling of about a dozen threads. It then refused connections outright while nineteen idle eepsites held their reservations. Those idle pools were not consuming threads anyway — allowCoreThreadTimeOut(true) reclaims them after the keepalive — so the budget bought contention and nothing else.

      Sizing is the load itself: every queued task needs a thread and every busy thread already has one, so queueDepth + active + headroom is both the minimum that keeps up and the size at which nothing queues. A dormant tunnel sits at SERVER_HANDLER_IDLE and grows on its own demand. The per-tunnel ceiling still applies, so one destination cannot take the whole machine.

      Pure decision, no router context, so the policy is testable.

      Parameters:
      queueDepth - tasks waiting for a handler thread
      active - handler threads currently busy
      cap - this tunnel's ceiling (override or Tuner default)
      Returns:
      worker threads for this pool, in [1, cap]
    • scaleDemandsToCeiling

      static int[] scaleDemandsToCeiling(int[] demand, int ceiling)
      Scale per-pool demands down to a global ceiling, proportionally.

      A safety valve against exhausting the heap, not a budget the pools compete for: each pool is sized by its own demand first, and only if the total is genuinely unsupportable is every pool trimmed together. Trimming is proportional to demand, so the busiest tunnel keeps the largest share and no pool drops below SERVER_HANDLER_IDLE.

      Pure, so the allocation policy is testable without a group.

      Parameters:
      demand - per-pool thread demand, indexed to match the pools
      ceiling - total handler threads the JVM can support
      Returns:
      per-pool allocation, each in [1, its own demand]
    • createServerExecutor

      static ThreadPoolExecutor createServerExecutor(int threads, AtomicLong index)
      Create a bounded executor that runs inbound server-tunnel connection handlers.

      Handlers are dispatched off the accept thread so a single slow connection cannot stall connection admission. Overflow uses ThreadPoolExecutor.AbortPolicy: when the fixed worker set and its bounded queue are saturated, the submission throws RejectedExecutionException instead of executing the handler inline on the caller. Inline execution (the former CallerRunsPolicy) would run a write-blocked handler on the I2PTunnelServer.run() accept loop, pinning accept(), which stops draining the streaming SYN queue and makes every fresh connection attempt expire and be RESET under load. The rejection is surfaced to I2PTunnelServer.run(), which closes the connection but keeps accepting new ones.

      Exposed package-visible (static) so tests can assert the overflow semantics without a live I2PAppContext.

      Parameters:
      threads - the fixed core/max worker count
      index - an AtomicLong counter used to name worker threads
      Returns:
      a never-null, bounded executor with AbortPolicy
    • createServerExecutor

      static ThreadPoolExecutor createServerExecutor(int threads, AtomicLong index, long keepAliveMs)
      As createServerExecutor(int, AtomicLong) but with an explicit worker keepalive.

      The production keepalive is SERVER_KEEPALIVE_MS (30s), which a test cannot wait out: the I2PTunnel suite is run with a 15s per-test timeout, so asserting on core-thread expiry at the real interval always fails on the clock rather than on the behaviour. Injecting a short keepalive lets the test assert the same thing in milliseconds.

      Parameters:
      threads - the fixed core/max worker count
      index - an AtomicLong counter used to name worker threads
      keepAliveMs - idle time before a core thread may be reclaimed
      Returns:
      a never-null, bounded executor with AbortPolicy
    • resizeServerExecutor

      private static void resizeServerExecutor(ThreadPoolExecutor ex, int newThreads)
      Resize a server-tunnel handler pool, preserving a fixed core == max pool. Called by rebalanceServerExecutors().
      Parameters:
      ex - the pool; ignored if null or shut down
      newThreads - the new fixed size
    • ensureServerHandlerStats

      private void ensureServerHandlerStats()
      Register the server-handler rate stats exactly once, on first pool use.
    • killServerExecutor

      private void killServerExecutor()
      Shutdown the per-tunnel server handler pools. Queued tasks drain and the worker threads exit; the registry is cleared so a group restart starts fresh pools.
    • killClientExecutor

      private void killClientExecutor()
      Shutdown the client executor and all per-tunnel runner pools, waiting for termination of the shared fallback pool.
    • resizeClientExecutor

      void resizeClientExecutor(int newMax)
      Resize the shared (fallback) client executor max pool size. Called by Tuner for the null-tunnel path; per-tunnel pools are resized by rebalanceAllRunnerPools().
      Parameters:
      newMax - the new maximum pool size, applied only while the shared executor is running