C35: every quit names its source (Cmd::Quit carries it: the window host's stdin, the host gone, POST /api/quit, the --sweep run's end); the --sweep job never counts as Power control and sets no cap at start (it raised a UAC prompt on PC 1 at 22:30 UTC); the tune playbook's budget quit goes only to its own scratch URL file and says so
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
f76fca96e2
commit
d69e1c9fae
4 changed files with 37 additions and 13 deletions
|
|
@ -75,7 +75,8 @@ pub enum Cmd {
|
|||
/// restart the node with the verifier decided again (src/verifier.rs): the trust setting changed, or the
|
||||
/// prover found a host that was not there when the node started
|
||||
RestartNode(String),
|
||||
Quit,
|
||||
/// quit, with its source (the log names it: C35, 5 October 2026, two unexplained quits)
|
||||
Quit(&'static str),
|
||||
}
|
||||
|
||||
pub struct Shared {
|
||||
|
|
@ -463,6 +464,8 @@ pub struct Engine {
|
|||
last_error_event: Instant,
|
||||
// the efficiency sweep (src/sweep.rs): one card at a time
|
||||
sweep: Option<crate::ember::Run>,
|
||||
/// who asked for the quit (the log's `quit:` line names it)
|
||||
quit_source: &'static str,
|
||||
/// the request number the vendor tool last carried out (the run's acknowledgement)
|
||||
tune_acked: Option<u64>,
|
||||
/// cards whose confirm check found a better neighbour: the full plan runs next
|
||||
|
|
@ -559,6 +562,7 @@ impl Engine {
|
|||
last_settings_save: now,
|
||||
last_error_event: now - Duration::from_secs(600),
|
||||
sweep: None,
|
||||
quit_source: "unknown",
|
||||
tune_acked: None,
|
||||
tune_full_due: std::collections::HashSet::new(),
|
||||
sweep_pending: None,
|
||||
|
|
@ -702,7 +706,7 @@ impl Engine {
|
|||
if self.shared.runtime.sweep_only {
|
||||
if supported.is_empty() {
|
||||
self.sweep_say("SWEEP none reason=no_supported_card");
|
||||
self.shared.send(Cmd::Quit);
|
||||
self.shared.send(Cmd::Quit("the --sweep run (every card done)"));
|
||||
} else {
|
||||
self.sweep_queue = supported;
|
||||
}
|
||||
|
|
@ -1021,7 +1025,9 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
},
|
||||
Cmd::Quit => {
|
||||
Cmd::Quit(source) => {
|
||||
self.shared.log(&format!("quit requested by {source}"));
|
||||
self.quit_source = source;
|
||||
self.quitting = true;
|
||||
self.st().quitting = true;
|
||||
}
|
||||
|
|
@ -1472,6 +1478,10 @@ impl Engine {
|
|||
if self.power_busy {
|
||||
return;
|
||||
}
|
||||
if self.shared.runtime.sweep_only {
|
||||
self.shared.log(&format!("power cap ({why}): not touched under --sweep; the tune sets every limit itself"));
|
||||
return;
|
||||
}
|
||||
if self.sweep.is_some() || self.sweep_pending.is_some() {
|
||||
// the sweep owns the caps until it ends; it applies the chosen one itself
|
||||
self.shared.log(&format!("power cap ({why}): deferred, a sweep is running"));
|
||||
|
|
@ -1856,7 +1866,7 @@ impl Engine {
|
|||
if let Some((idx, forced)) = pick {
|
||||
self.sweep_begin(idx, forced);
|
||||
} else if self.shared.runtime.sweep_only && self.sweep_queue.is_empty() && self.sweep_pending.is_none() {
|
||||
self.shared.send(Cmd::Quit);
|
||||
self.shared.send(Cmd::Quit("the --sweep run (every card done)"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2304,7 +2314,7 @@ impl Engine {
|
|||
}
|
||||
self.upload_logs(false);
|
||||
if self.shared.runtime.sweep_only && self.sweep_queue.is_empty() {
|
||||
self.shared.send(Cmd::Quit);
|
||||
self.shared.send(Cmd::Quit("the --sweep run (every card done)"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2356,7 +2366,7 @@ impl Engine {
|
|||
} else {
|
||||
self.sweep_retry.insert(idx, Instant::now() + Duration::from_secs(3600));
|
||||
if self.shared.runtime.sweep_only && self.sweep_queue.is_empty() {
|
||||
self.shared.send(Cmd::Quit);
|
||||
self.shared.send(Cmd::Quit("the --sweep run (every card done)"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3371,7 +3381,7 @@ impl Engine {
|
|||
}
|
||||
|
||||
fn shutdown(&mut self) {
|
||||
self.shared.log("quit: stopping the miners, then the node");
|
||||
self.shared.log(&format!("quit: stopping the miners, then the node (source: {})", self.quit_source));
|
||||
self.jobs.abort(&self.shared, "the app is quitting");
|
||||
if self.sweep.is_some() || self.sweep_pending.is_some() {
|
||||
self.sweep_abort("the app is quitting");
|
||||
|
|
@ -3426,7 +3436,11 @@ impl Engine {
|
|||
/// administrator rights (one UAC prompt on Windows, pkexec on Linux); the engine builds an elevated command only when
|
||||
/// Power control is on in Settings, or when it is itself the elevated PC sweep job (--sweep).
|
||||
fn elevation_allowed(power_control: bool, sweep_only: bool) -> bool {
|
||||
power_control || sweep_only
|
||||
// C35 (5 October 2026, 22:30 UTC): the unattended --sweep job on PC 1 counted as allowed and raised the one
|
||||
// administrator prompt nobody was there to answer; an elevated job sets limits directly without asking (the tune
|
||||
// probe's `direct`), so the flag adds nothing and Power control alone decides
|
||||
let _ = sweep_only;
|
||||
power_control
|
||||
}
|
||||
|
||||
/// The notice when the one prompt was refused, cancelled or not answered: Power control goes back off, no retries.
|
||||
|
|
@ -3634,7 +3648,7 @@ mod tests {
|
|||
// the decision (the project lead, 5 October 2026): off = the app never asks; the elevated PC sweep job is the exception
|
||||
assert!(!super::elevation_allowed(false, false));
|
||||
assert!(super::elevation_allowed(true, false));
|
||||
assert!(super::elevation_allowed(false, true));
|
||||
assert!(!super::elevation_allowed(false, true), "the --sweep job alone never asks (C35)");
|
||||
let mut cards = vec![
|
||||
super::CardState { vendor: "nvidia".into(), enabled: true, device: "0".into(), name: "RTX 5090".into(), power_default_w: 575.0, power_limit_w: 575.0, power_pct: 80, ..Default::default() },
|
||||
super::CardState { vendor: "amd".into(), enabled: true, device: "1".into(), name: "RX 9070 XT".into(), power_default_w: 300.0, power_limit_w: 300.0, power_pct: 80, ..Default::default() },
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ fn main() {
|
|||
let Ok(l) = line else { break };
|
||||
let t = l.trim();
|
||||
match t {
|
||||
"quit" => shared.send(engine::Cmd::Quit),
|
||||
"quit" => shared.send(engine::Cmd::Quit("the window host (quit on stdin: the tray menu or the installer)")),
|
||||
"pause" => shared.send(engine::Cmd::Pause),
|
||||
"resume" => shared.send(engine::Cmd::Resume),
|
||||
"elevated ok" => shared.send(engine::Cmd::ElevatedDone(Ok(()))),
|
||||
|
|
@ -142,7 +142,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
if wrapper {
|
||||
shared.send(engine::Cmd::Quit);
|
||||
shared.send(engine::Cmd::Quit("the window host went away (stdin closed)"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -339,7 +339,8 @@ fn api_post(shared: &Arc<Shared>, path: &str, body: Value) -> Result<Value, Stri
|
|||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
"/api/quit" => {
|
||||
shared.send(Cmd::Quit);
|
||||
// the caller is on 127.0.0.1 and holds the token: the installer, the OTA apply, a script that read app.url
|
||||
shared.send(Cmd::Quit("POST /api/quit (a local caller with the token: the installer, the OTA apply, or a script that read app.url)"));
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
_ => Err("unknown api".into()),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
# therefore measure only tonight unless the engine finds itself elevated.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$budgetMinutes = 35
|
||||
if (-not ($budgetMinutes -is [int]) -or $budgetMinutes -lt 5) { $budgetMinutes = 35 } # a budget under 5 minutes is a bug, not a budget (C35)
|
||||
$started = Get-Date
|
||||
$deadline = $started.AddMinutes($budgetMinutes)
|
||||
function Say([string] $m) { Write-Host ("[" + (Get-Date -Format 'HH:mm:ss') + "] " + $m) }
|
||||
|
|
@ -123,8 +124,16 @@ while (-not $p.HasExited) {
|
|||
}
|
||||
if ((Get-Date) -gt $deadline) {
|
||||
Say ("budget of " + $budgetMinutes + " min spent; asking the tune engine to quit")
|
||||
# C35 (5 October 2026): the only quit this script may send goes to the TUNE engine's own URL file in the scratch
|
||||
# root, never to a file under the installed app's folder; the RESULT line names the file it used
|
||||
$u = Join-Path $sApp 'app.url'
|
||||
if (Test-Path $u) { try { Invoke-WebRequest -Uri ((Get-Content -LiteralPath $u -Raw).Trim() + 'api/quit') -Method POST -Body '{}' -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null } catch { } }
|
||||
$installedUrl = Join-Path $appDir 'app.url'
|
||||
if ((Resolve-Path -LiteralPath $u -ErrorAction SilentlyContinue).Path -eq (Resolve-Path -LiteralPath $installedUrl -ErrorAction SilentlyContinue).Path -or $u -like '*\igneum\app\*') {
|
||||
Write-Output ('RESULT TUNE quit refused: ' + $u + ' is the installed app''s URL file')
|
||||
} elseif (Test-Path -LiteralPath $u) {
|
||||
Write-Output ('RESULT TUNE quit asked of the tune engine through ' + $u + ' (pid ' + $p.Id + ')')
|
||||
try { Invoke-WebRequest -Uri ((Get-Content -LiteralPath $u -Raw).Trim() + 'api/quit') -Method POST -Body '{}' -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null } catch { }
|
||||
} else { Write-Output ('RESULT TUNE quit not sent: no URL file at ' + $u + '; killing pid ' + $p.Id) }
|
||||
Start-Sleep -Seconds 20
|
||||
if (-not $p.HasExited) { $p.Kill() }
|
||||
Write-Output 'RESULT TUNE error=budget_exceeded'
|
||||
|
|
|
|||
Loading…
Reference in a new issue