Class CanConfigBudget

java.lang.Object
frc.spectrumLib.hardware.CanConfigBudget

public final class CanConfigBudget extends Object
A cap on how long boot may spend on CAN device configuration that is not working.

Every Phoenix configuration call blocks until the device answers or the timeout expires. That is invisible when the bus is healthy. When it is not, the cost multiplies: each of the roughly two dozen CANivore devices is configured with several such calls, plus retry loops on top, and every one of those waits out its full timeout against a device that is not there.

In the 2026-09-19 Chezy practice match the CANivore bus went down mid-match on a cut wire. The roboRIO was restarted from the Driver Station and the code looked like it never came back: robot init took 64.0 s against 16.9 s in the match before it, and the rio then sat at 93-99% CPU with 58-77% of loops over 25 ms. It had in fact come back. It was just unusable, and the match was over before anyone could tell the difference.

This class bounds that. Callers run their configuration through run(String, DoubleFunction) and consult exhausted() before retrying or before making calls that are merely nice to have. Once the cumulative time lost to failed configuration passes BUDGET_SECONDS, the budget is spent: retries stop, optional calls are skipped, and an alert names it. Boot then completes with a dead bus in roughly the time it takes to fail once per device instead of ten times.

Open question, 2026-09-19. This bounds the boot, but it is not the whole of what happened after that restart. With the bus already dead, CPU sat at a normal 40 to 50 percent for 20 s after init, then went to 95 to 100 percent at exactly 30.0 s after init completed and stayed there, with 58 to 77 percent of loops over 25 ms. Nothing in the Java loop changed at that moment (vision and the scheduler were each under 20 ms) and the DataLog writer thread was starved too, so the load is native or a background service, not the robot loop. Bench test to find it: unplug the CANivore, boot, watch System/CpuPercent at plus 30 s, then try disabling the Phoenix diagnostics server (Unmanaged.setPhoenixDiagnosticsStartTime(-1)) and the SignalLogger to see which one it is. Log: logs/matches/FRC_20260919_034444.wpilog.

The cap is deliberately cause-agnostic. A cut wire, a powered-down bus, a wrong bus name and a missing CANivore all present identically here, and the correct response to all of them is the same: stop waiting and come up.

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final double
    Cumulative seconds of failed configuration allowed before retries are abandoned.
    static final double
    Timeout for a single boot configuration call, in seconds.
    static final int
    Retry count used while the budget holds.
  • Method Summary

    Modifier and Type
    Method
    Description
    static void
    exhaust(String reason)
    Spends the whole budget at once, for a bus known to be unreachable before any device is configured (e.g.
    static boolean
    Whether the budget is spent and callers should stop retrying.
    static int
    Number of failed configuration calls so far.
    static double
    Seconds charged to the budget so far.
    static int
    Number of attempts a caller should make for one configuration.
    static StatusCode
    Runs one configuration call with the bounded timeout and charges its cost to the budget when it fails.

    Methods inherited from class Object

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

    • BUDGET_SECONDS

      public static final double BUDGET_SECONDS
      Cumulative seconds of failed configuration allowed before retries are abandoned.

      Generous enough that a single slow device, or a handful of first-call timeouts on a healthy bus, never trips it; small enough that a fully dead bus costs a few seconds rather than the better part of a minute.

      See Also:
    • CALL_TIMEOUT_SECONDS

      public static final double CALL_TIMEOUT_SECONDS
      Timeout for a single boot configuration call, in seconds.

      Phoenix's own default is 0.050 s. This is deliberately the same: the fix here is not a shorter individual wait, which would risk failing on a healthy-but-busy bus, but refusing to repeat the wait once it is clear nothing is answering.

      See Also:
    • MAX_ATTEMPTS

      public static final int MAX_ATTEMPTS
      Retry count used while the budget holds.
      See Also:
  • Method Details

    • exhausted

      public static boolean exhausted()
      Whether the budget is spent and callers should stop retrying.
      Returns:
      true once failed configuration has cost more than BUDGET_SECONDS
    • maxAttempts

      public static int maxAttempts()
      Number of attempts a caller should make for one configuration.
      Returns:
      MAX_ATTEMPTS while the budget holds, otherwise 1
    • run

      public static StatusCode run(String name, DoubleFunction<StatusCode> call)
      Runs one configuration call with the bounded timeout and charges its cost to the budget when it fails.

      A successful call costs nothing, however long it took: time spent talking to a device that is really there is time well spent. Only failures are charged, because only failures repeat.

      Parameters:
      name - the device or mechanism being configured, for the alert text
      call - the configuration call, taking the timeout in seconds
      Returns:
      the status the call returned
    • exhaust

      public static void exhaust(String reason)
      Spends the whole budget at once, for a bus known to be unreachable before any device is configured (e.g. no CANivore found). Every later call then fails once instead of retrying.
      Parameters:
      reason - what was found, for the alert
    • getSpentSeconds

      public static double getSpentSeconds()
      Seconds charged to the budget so far. Logged so a boot that nearly tripped the cap is visible before the one that trips it.
      Returns:
      cumulative seconds lost to failed configuration
    • getFailedCalls

      public static int getFailedCalls()
      Number of failed configuration calls so far.
      Returns:
      the count of failed calls