fix(sidecar): use ephemeral ports for tcp/quic listen too
mycelium defaults --tcp-listen-port and --quic-listen-port to 9651 when not provided. If anything else holds 9651 (a previous test instance, a Docker container from the level-1 procedure, another mycelium running on the host), the daemon comes up far enough to serve the loopback API for a few seconds before tearing itself down on the listen failure. Pick three distinct ephemeral ports (api, tcp, quic) per spawn. Trade-off: inbound peers need the actual port number, which we already log; the user can pin via a future SidecarConfig field.
This commit is contained in:
@@ -102,8 +102,14 @@ impl SidecarHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bin = locate_sidecar(app)?;
|
let bin = locate_sidecar(app)?;
|
||||||
let port = portpicker::pick_unused_port()
|
// Three ports: HTTP API (loopback), TCP listen, QUIC (UDP) listen.
|
||||||
.ok_or_else(|| AppError::Other("no free port available".into()))?;
|
// mycelium defaults to 9651 for both peer-listen ports, which
|
||||||
|
// collides if another instance (or a leftover from a previous test)
|
||||||
|
// is already up. Always picking ephemeral ports avoids that at the
|
||||||
|
// cost of inbound peers needing the actual port number.
|
||||||
|
let api_port = pick_port()?;
|
||||||
|
let tcp_port = pick_port_skip(&[api_port])?;
|
||||||
|
let quic_port = pick_port_skip(&[api_port, tcp_port])?;
|
||||||
|
|
||||||
let data_dir = app
|
let data_dir = app
|
||||||
.path()
|
.path()
|
||||||
@@ -115,7 +121,11 @@ impl SidecarHandle {
|
|||||||
|
|
||||||
let mut args = vec![
|
let mut args = vec![
|
||||||
"--api-addr".to_string(),
|
"--api-addr".to_string(),
|
||||||
format!("127.0.0.1:{port}"),
|
format!("127.0.0.1:{api_port}"),
|
||||||
|
"--tcp-listen-port".to_string(),
|
||||||
|
tcp_port.to_string(),
|
||||||
|
"--quic-listen-port".to_string(),
|
||||||
|
quic_port.to_string(),
|
||||||
"--key-file".to_string(),
|
"--key-file".to_string(),
|
||||||
key_path.display().to_string(),
|
key_path.display().to_string(),
|
||||||
];
|
];
|
||||||
@@ -137,7 +147,11 @@ impl SidecarHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(?bin, port, "spawning mycelium sidecar via pkexec");
|
info!(
|
||||||
|
?bin,
|
||||||
|
api_port, tcp_port, quic_port,
|
||||||
|
"spawning mycelium sidecar via pkexec"
|
||||||
|
);
|
||||||
|
|
||||||
let mut cmd = elevation::elevated(&bin, &args);
|
let mut cmd = elevation::elevated(&bin, &args);
|
||||||
cmd.stdout(Stdio::piped())
|
cmd.stdout(Stdio::piped())
|
||||||
@@ -150,7 +164,7 @@ impl SidecarHandle {
|
|||||||
|
|
||||||
// Stash before we await the health check, so a slow daemon
|
// Stash before we await the health check, so a slow daemon
|
||||||
// doesn't leave us with a zombie process if anything panics.
|
// doesn't leave us with a zombie process if anything panics.
|
||||||
let api_url = format!("http://127.0.0.1:{port}");
|
let api_url = format!("http://127.0.0.1:{api_port}");
|
||||||
*self.child.lock() = Some(child);
|
*self.child.lock() = Some(child);
|
||||||
*self.api_url.lock() = Some(api_url.clone());
|
*self.api_url.lock() = Some(api_url.clone());
|
||||||
*self.config_path.lock() = Some(config_path);
|
*self.config_path.lock() = Some(config_path);
|
||||||
@@ -257,6 +271,22 @@ impl SidecarHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pick_port() -> AppResult<u16> {
|
||||||
|
portpicker::pick_unused_port().ok_or_else(|| AppError::Other("no free port available".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_port_skip(taken: &[u16]) -> AppResult<u16> {
|
||||||
|
for _ in 0..16 {
|
||||||
|
let p = pick_port()?;
|
||||||
|
if !taken.contains(&p) {
|
||||||
|
return Ok(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(AppError::Other(
|
||||||
|
"could not find a unique free port".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve the bundled `mycelium-<triple>` binary in both `tauri dev`
|
/// Resolve the bundled `mycelium-<triple>` binary in both `tauri dev`
|
||||||
/// (cargo manifest) and bundled (resource_dir) modes.
|
/// (cargo manifest) and bundled (resource_dir) modes.
|
||||||
fn locate_sidecar(app: &AppHandle) -> AppResult<PathBuf> {
|
fn locate_sidecar(app: &AppHandle) -> AppResult<PathBuf> {
|
||||||
|
|||||||
Reference in New Issue
Block a user