From 7a884871314511957dcabfa01637023a3520d9d1 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Fri, 19 Dec 2025 19:55:48 -0600 Subject: [PATCH] Wait for NetworkManager on first call to getAllInterfaces (#2240) ## Description On the RubikPi3, PhotonVision starts before NetworkManager has fully initialized. This causes it to fail to identify the network interfaces available on the board, which leads to problems with configuring and controling networking from the UI. The failure can be detected by the call to `nmcli` returning an [exit status of 8](https://networkmanager.dev/docs/api/latest/nmcli.html#exit_status), which means "NetworkManager is not running." This PR retries the call to nmcli every 0.5 seconds until the exit status does not equal 8, or a maximum of 10 attempts have been made. The retry only occurs the first time `getAllInterfaces()` is called. Subsequent calls to this method will only make one attempt to avoid locking up the program if networking isn't responding as expected. In my testing on the RubikPi3, the code has to retry for less than 2 seconds in order to get a valid response from NetworkManager. The need for this is greatly reduced by https://github.com/PhotonVision/photon-image-modifier/pull/114, but this code adds an additional layer of robustness against slow network startup. Closes #2212 ## Meta Merge checklist: - [x] Pull Request title is [short, imperative summary](https://cbea.ms/git-commit/) of proposed changes - [x] The description documents the _what_ and _why_ - [ ] If this PR changes behavior or adds a feature, user documentation is updated - [ ] If this PR touches photon-serde, all messages have been regenerated and hashes have not changed unexpectedly - [ ] If this PR touches configuration, this is backwards compatible with settings back to v2025.3.2 - [ ] If this PR touches pipeline settings or anything related to data exchange, the frontend typing is updated - [ ] If this PR addresses a bug, a regression test for it is added --------- Co-authored-by: samfreund --- .../common/networking/NetworkUtils.java | 43 +++++++++++++++---- .../src/main/java/org/photonvision/Main.java | 8 ++-- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java index ac3e29326..584c4be5e 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java @@ -82,23 +82,48 @@ public class NetworkUtils { private static List allInterfaces = null; private static long lastReadTimestamp = 0; + private static long timeout = 5000; // milliseconds + private static long retry = 500; // milliseconds - public static List getAllInterfaces() { - long now = System.currentTimeMillis(); - if (now - lastReadTimestamp < 5000) return allInterfaces; - else lastReadTimestamp = now; - + public static synchronized List getAllInterfaces() { + var start = System.currentTimeMillis(); + if (start - lastReadTimestamp < 5000) { + return allInterfaces; + } var ret = new ArrayList(); if (Platform.isLinux()) { String out = null; try { var shell = new ShellExec(true, false); - shell.executeBashCommand( - "nmcli -t -f GENERAL.CONNECTION,GENERAL.DEVICE,GENERAL.TYPE device show", true, false); - out = shell.getOutput(); + boolean networkManagerRunning = false; + boolean tryagain = true; + + do { + shell.executeBashCommand( + "nmcli -t -f GENERAL.CONNECTION,GENERAL.DEVICE,GENERAL.TYPE device show", true, true); + // nmcli returns an error of 8 if NetworkManager isn't running + networkManagerRunning = shell.getExitCode() != 8; + tryagain = System.currentTimeMillis() - start < timeout; + if (!networkManagerRunning && tryagain) { + logger.debug("NetworkManager not running, retrying in " + (retry) + " milliseconds"); + Thread.sleep(retry); + } + } while (!networkManagerRunning && tryagain); + + timeout = 0; // only try once after the first time + + if (networkManagerRunning) { + out = shell.getOutput(); + } else { + logger.error( + "Timed out trying to reach NetworkManager, may not be able to configure networking"); + } + } catch (IOException e) { logger.error("IO Exception occured when calling nmcli to get network interfaces!", e); + } catch (InterruptedException e) { + logger.error("Interrupted while waiting for NetworkManager", e); } if (out != null) { Pattern pattern = @@ -120,6 +145,8 @@ public class NetworkUtils { } allInterfaces = ret; } + lastReadTimestamp = System.currentTimeMillis(); + return ret; } diff --git a/photon-server/src/main/java/org/photonvision/Main.java b/photon-server/src/main/java/org/photonvision/Main.java index c11bc0203..6191fa501 100644 --- a/photon-server/src/main/java/org/photonvision/Main.java +++ b/photon-server/src/main/java/org/photonvision/Main.java @@ -276,10 +276,6 @@ public class Main { ConfigManager.getInstance().load(); // init config manager ConfigManager.getInstance().requestSave(); - logger.debug("Loading HardwareManager..."); - // Force load the hardware manager - HardwareManager.getInstance(); - logger.info("Loading ML models..."); var modelManager = NeuralNetworkModelManager.getInstance(); modelManager.extractModels(); @@ -293,6 +289,10 @@ public class Main { .setConfig(ConfigManager.getInstance().getConfig().getNetworkConfig()); NetworkTablesManager.getInstance().registerTimedTasks(); + logger.debug("Loading HardwareManager..."); + // Force load the hardware manager + HardwareManager.getInstance(); + if (isSmoketest) { logger.info("PhotonVision base functionality loaded -- smoketest complete"); System.exit(0);