Class CanConfigBudget
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
FieldsModifier and TypeFieldDescriptionstatic final doubleCumulative seconds of failed configuration allowed before retries are abandoned.static final doubleTimeout for a single boot configuration call, in seconds.static final intRetry count used while the budget holds. -
Method Summary
Modifier and TypeMethodDescriptionstatic voidSpends the whole budget at once, for a bus known to be unreachable before any device is configured (e.g.static booleanWhether the budget is spent and callers should stop retrying.static intNumber of failed configuration calls so far.static doubleSeconds charged to the budget so far.static intNumber of attempts a caller should make for one configuration.static StatusCoderun(String name, DoubleFunction<StatusCode> call) Runs one configuration call with the bounded timeout and charges its cost to the budget when it fails.
-
Field Details
-
BUDGET_SECONDS
public static final double BUDGET_SECONDSCumulative 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_SECONDSTimeout 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_ATTEMPTSRetry 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_ATTEMPTSwhile the budget holds, otherwise 1
-
run
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 textcall- the configuration call, taking the timeout in seconds- Returns:
- the status the call returned
-
exhaust
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
-