fix(mesh): serialize serial-port opens between listener and RPC probe
Observed live on .116 after the dedup/backoff fixes: the kiosk browser's hot-swap auto-probe (mesh.probe-device) still interleaved its handshakes with the listener's open sequence on the same tty every backoff window — Linux double-opens ttys silently, both handshakes corrupted each other, and every collision's open() DTR/RTS-reset the board again. The retry heuristic from 5f01ec31 narrowed but couldn't close the race. A static PORT_OPEN_LOCK now covers the listener's whole device-open sequence and each probe attempt, so exactly one prober touches the port at a time. With this + the settle/backoff/dedup fixes, the .116 radio completed its first MeshCore handshake in days (node-fa161601, 8 contacts) and the session has been stable since. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c8d0dda656
commit
c0d34bd836
@ -372,6 +372,16 @@ pub struct DeviceProbe {
|
||||
pub max_contacts: Option<u16>,
|
||||
}
|
||||
|
||||
/// Serializes serial-port open sequences between the listener's session
|
||||
/// opens and the RPC probe (`mesh.probe-device`). Linux happily double-opens
|
||||
/// a tty, and two concurrent handshakes corrupt each other into silence —
|
||||
/// observed live on .116 (2026-07-26): the kiosk browser's hot-swap
|
||||
/// auto-probe collided with the listener's cycle on every backoff window, so
|
||||
/// neither ever succeeded, and each collision's open() DTR/RTS-reset the
|
||||
/// board again. The probe's retry-across-idle-gaps heuristic (5f01ec31)
|
||||
/// narrowed but could not close the race; this closes it.
|
||||
static PORT_OPEN_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
|
||||
|
||||
/// Probe a serial port for a mesh radio WITHOUT provisioning it: identify the
|
||||
/// firmware (same strict Reticulum→Meshcore→Meshtastic order as auto-detect,
|
||||
/// for the same RNode-wedging reason) and read what's currently configured on
|
||||
@ -379,11 +389,10 @@ pub struct DeviceProbe {
|
||||
/// loop can pick the device up afterwards. Reticulum uses the bare KISS
|
||||
/// DETECT probe — no daemon spawn just to identify a stick.
|
||||
pub(crate) async fn probe_device(path: &str) -> Result<DeviceProbe> {
|
||||
// The listener's reconnect loop may hold this port for ~10s of every
|
||||
// backoff cycle, and Linux happily double-opens a tty — two concurrent
|
||||
// handshakes corrupt each other into silence (observed on framework-pt:
|
||||
// every firmware "failed" while the listener was mid-cycle). Retry across
|
||||
// the listener's idle gaps instead of failing on first collision.
|
||||
// Retries kept even with PORT_OPEN_LOCK closing the double-open race:
|
||||
// the board may still be mid-boot from a previous open's DTR/RTS reset,
|
||||
// and a later attempt after a quiet gap can succeed where the first
|
||||
// couldn't.
|
||||
let mut last_err = None;
|
||||
for attempt in 0..3u32 {
|
||||
if attempt > 0 {
|
||||
@ -398,6 +407,7 @@ pub(crate) async fn probe_device(path: &str) -> Result<DeviceProbe> {
|
||||
}
|
||||
|
||||
async fn probe_device_once(path: &str) -> Result<DeviceProbe> {
|
||||
let _port_guard = PORT_OPEN_LOCK.lock().await;
|
||||
if super::super::reticulum::probe_rnode(path).await.is_ok() {
|
||||
return Ok(DeviceProbe {
|
||||
path: path.to_string(),
|
||||
@ -976,6 +986,11 @@ pub(super) async fn run_mesh_session(
|
||||
// set, otherwise try the preferred serial path, falling back to
|
||||
// auto-detect. TCP mode is additive/dev-only; it never changes behavior
|
||||
// for existing serial/RNode deployments where `reticulum_tcp` is None.
|
||||
//
|
||||
// The whole open sequence runs under PORT_OPEN_LOCK so an RPC probe
|
||||
// can't interleave its own handshakes on the same tty (see the lock's
|
||||
// doc comment). Held only until the device is opened, then released.
|
||||
let port_guard = PORT_OPEN_LOCK.lock().await;
|
||||
let (device_path, mut device, device_info) = if let Some(tcp_cfg) = &reticulum_tcp {
|
||||
open_reticulum_tcp(tcp_cfg, data_dir, our_ed_pubkey_hex, our_x25519_pubkey_hex).await?
|
||||
} else if let Some(path) = preferred_path {
|
||||
@ -1014,6 +1029,7 @@ pub(super) async fn run_mesh_session(
|
||||
)
|
||||
.await?
|
||||
};
|
||||
drop(port_guard);
|
||||
|
||||
// Update status
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user