fix(install): kick scanner post-install so Launch button appears immediately
After install completes, the async-spawn wrapper wrote state=Running
but the skeletal install-time manifest (interfaces: None) persisted
until the next scheduled 60s scan. The frontend saw state=running but
hasUI=false and hid the Launch button for up to a full minute.
Add a shared Notify/watch pair between RpcHandler and the scan loop:
- scan_kick (Notify): scan loop selects! between the 60s interval
and this notify, running immediately on either.
- scan_tick (watch<u64>): scan loop bumps the counter after each
completed scan so callers can await completion.
Install and update success paths now call kick_scanner_and_wait before
flipping to Running. The scan merges via merge_preserving_transitional
(state stays Installing/Updating, manifest refreshed from live podman
with interfaces.main.ui populated from real port bindings). 2s timeout
falls back to pre-fix behavior on slow podman — no regression.
This commit is contained in:
@@ -91,6 +91,13 @@ impl RpcHandler {
|
||||
info!("package.install {}: complete", package_id_spawn);
|
||||
// The install pipeline has verified the container is up
|
||||
// and healthy (see install.rs post-start exit check).
|
||||
// Kick the scanner first so the fresh manifest (with
|
||||
// `interfaces.main.ui` from the live port binding) lands
|
||||
// BEFORE we flip to Running — without this the Launch
|
||||
// button is missing for up to 60s after a successful
|
||||
// install, because the skeletal install-time manifest
|
||||
// has `interfaces: None`.
|
||||
kick_scanner_and_wait(&handler).await;
|
||||
// We MUST explicitly transition out of Installing here:
|
||||
// `merge_preserving_transitional` in the package-scan
|
||||
// loop treats Installing as RPC-owned and refuses to
|
||||
@@ -259,6 +266,10 @@ impl RpcHandler {
|
||||
// stuck at Updating forever. The update pipeline has
|
||||
// already verified the new container is running via its
|
||||
// post-recreate check.
|
||||
// Kick the scanner first so any manifest changes from the
|
||||
// new image version (interfaces, ports, etc.) land before
|
||||
// we flip to Running.
|
||||
kick_scanner_and_wait(&handler).await;
|
||||
set_package_state(
|
||||
&handler.state_manager,
|
||||
&package_id_spawn,
|
||||
@@ -406,3 +417,34 @@ async fn remove_package_entry(state_manager: &StateManager, package_id: &str) {
|
||||
state_manager.update_data(data).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Kick the container scanner to run immediately and wait for it to finish
|
||||
/// (with a 2s timeout). Used by install/update success paths so the fresh
|
||||
/// manifest — with `interfaces.main.ui` populated from the now-running
|
||||
/// container's port binding — lands BEFORE we flip state to Running.
|
||||
///
|
||||
/// Without this, the frontend sees `state = running` but the skeletal
|
||||
/// install-time manifest (interfaces = None), and hides the Launch button
|
||||
/// for up to the full 60s scan interval.
|
||||
///
|
||||
/// The scan merges via `merge_preserving_transitional`, which keeps
|
||||
/// state = Installing (we haven't flipped yet) while taking the fresh
|
||||
/// manifest. After this returns, the caller writes Running on top of the
|
||||
/// now-populated manifest.
|
||||
async fn kick_scanner_and_wait(handler: &RpcHandler) {
|
||||
let mut rx = handler.scan_tick.subscribe();
|
||||
let start = *rx.borrow_and_update();
|
||||
handler.scan_kick.notify_one();
|
||||
// 2s is well above a typical podman scan (~200ms on .228, ~500ms worst
|
||||
// case). If it times out we proceed anyway — the next 60s scan will
|
||||
// self-heal and the worst case is the pre-fix behavior (Launch button
|
||||
// appears a bit late).
|
||||
let _ = tokio::time::timeout(std::time::Duration::from_secs(2), async {
|
||||
while *rx.borrow_and_update() == start {
|
||||
if rx.changed().await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user