v0.151.0 — the Megosztás page stops reloading, and says how to connect
S-1: /sharing/status coerced idle->running on the PHASE channel, so the first poll of every steady-state page load reported a terminal job that never ran and the client's repaint-reload fired ~1.2s apart, forever. The coercion's real duty (liveness must never be contradicted) belongs to the 'running' LEVEL field beside it, and is now pinned by its own regression test. S-4 core: a terminal 'running' is served exactly once, so a REAL bring-up cannot re-arm the reload on the page it just caused. failed/needs_password/in-flight are never consumed. Unified async-job feedback stays the ROADMAP item. S-2/S-5: new connect card with the Windows form, the Mac form and the direct smb://<IP>, read from the SAMBA container's netns (the controller is on a docker bridge and would answer 172.x). Derived per render, cached nowhere - the address is a DHCP lease. Underivable => the line is omitted. sharing.html's <script> block is byte-identical to v0.150.0. Red-proofed three ways. 23/23 packages green.
This commit is contained in:
@@ -92,6 +92,41 @@ func (s *sambaEnsureState) snapshot() *sambaEnsureJob {
|
||||
return &cp
|
||||
}
|
||||
|
||||
// consumeIfRunning is snapshot() with SERVE-ONCE semantics for the one phase the client reads as an
|
||||
// edge rather than a level.
|
||||
//
|
||||
// `running` means "the bring-up you were watching has finished" — the client answers it by repainting
|
||||
// the page (a full reload, because the „Állapot" badge is server-rendered). A phase that stays
|
||||
// `running` in the snapshot therefore re-arms that reload on every subsequent page load: the loop
|
||||
// S-1 fixed by dropping the idle→running coercion would come straight back after the next REAL
|
||||
// bring-up, since the finished job outlives it in memory (S-4 core,
|
||||
// felhom.eu/documentation/audits/DIAG-sharing-2026-07-20.md). Reporting it exactly once is what
|
||||
// makes the edge an edge.
|
||||
//
|
||||
// Consumed: terminal `running`, and only while the single-flight slot is free — the job goroutine
|
||||
// sets the phase before its deferred release(), and eating it inside that window would lose the
|
||||
// success the customer is waiting for.
|
||||
//
|
||||
// NOT consumed: `failed` and `needs_password` (durable explanations — the client stops the timer and
|
||||
// shows a card, with no reload, so stickiness is informative and cannot loop) and every in-flight
|
||||
// phase (`pulling`/`starting`, which must survive being polled).
|
||||
//
|
||||
// Accepted cost: with two tabs open on /sharing during a bring-up, whichever polls first gets the
|
||||
// success banner and the other sees plain `idle`. Both then show the true state — `running` is still
|
||||
// on the level channel and the badge re-renders from the liveness probe either way.
|
||||
func (s *sambaEnsureState) consumeIfRunning() *sambaEnsureJob {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.cur == nil {
|
||||
return nil
|
||||
}
|
||||
cp := *s.cur
|
||||
if !s.running && cp.Phase == sambaPhaseRunning {
|
||||
s.cur = nil
|
||||
}
|
||||
return &cp
|
||||
}
|
||||
|
||||
// startSambaEnsure claims the single-flight slot and launches the detached reconcile. false = one is
|
||||
// already in flight (a double-submit must not start a second compose up on the same stack dir).
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user