From 1e087bb158ac482d5b8a48c9901ce8c4bfd0eff9 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sat, 24 Oct 2026 13:24:41 +0200 Subject: [PATCH] linetest: waiting_for_quiet needs the full watched window (R-1250) The live proof found a fresh agent answering waiting before it had watched one minute; a quiet box's torrent app would be held for nothing. --- CHANGELOG.md | 3 +++ internal/linetest/linetest.go | 3 +++ internal/linetest/linetest_test.go | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0995b7..def44d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ line and qBittorrent never got its 50 % limit. Now: - **`linetest.Tester.WaitingForQuiet`** — due, inside the window, tries left, tunnel up, and only the quiet missing. `GET /host/line-test` carries it as `waiting_for_quiet`; a controller with a seeding torrent app acts only on it. + It needs the full watched window (11 minute samples): a freshly started agent is not known to be busy, so it never + invites a hold (found in the live proof, 2026-10-24: the first build said `true` before one minute was watched; + `TestFreshTesterIsNotWaiting`). - **`linetest.Tester.RunNow`** and **`POST /host/line-test/run`** (synchronous, about 55 s) — a test on request while the controller holds its app's upload low: every weekly rule holds (due, window, `MaxTriesPerDay`, tunnel, the one heavy-op gate) except the 10 quiet minutes, which become a 5 s settle and a 10 s quiet check (below 2 Mbit/s, so a request diff --git a/internal/linetest/linetest.go b/internal/linetest/linetest.go index 9b8ec13..805838a 100644 --- a/internal/linetest/linetest.go +++ b/internal/linetest/linetest.go @@ -336,6 +336,9 @@ func (t *Tester) WaitingForQuiet() bool { t.mu.Lock() samples := append([]txSample(nil), t.samples...) t.mu.Unlock() + if len(samples) < QuietMinutes+1 { + return false // not yet watched for 10 minutes (a fresh start): not known to be busy, so no hold + } q, _ := quiet(samples) return !q } diff --git a/internal/linetest/linetest_test.go b/internal/linetest/linetest_test.go index fa28f59..26e50cc 100644 --- a/internal/linetest/linetest_test.go +++ b/internal/linetest/linetest_test.go @@ -549,3 +549,26 @@ func TestQuietBoxIsNotWaiting(t *testing.T) { t.Fatal("ten quiet minutes: the weekly Tick runs; waiting_for_quiet must be false") } } + +// Live proof 2026-10-24 (demo-hp): a freshly started tester answered waiting_for_quiet before it had watched a single +// minute, so a quiet box's torrent app would be held for nothing. Waiting needs the full window, seen busy. +func TestFreshTesterIsNotWaiting(t *testing.T) { + r := &rig{run: &fakeRunner{}, gate: &fakeGate{}, now: time.Date(2026, 10, 20, 12, 0, 0, 0, time.UTC), tunnel: true} + r.t = New(r.run, func() (netip.Addr, bool) { return netip.MustParseAddr("10.77.0.1"), r.tunnel }, r.gate, + func() (uint64, error) { return r.tx, nil }, "host-x", filepath.Join(t.TempDir(), "line-test.json"), nil) + r.t.loc = time.UTC + r.t.now = func() time.Time { return r.now } + if r.t.WaitingForQuiet() { + t.Fatal("no minute watched yet: waiting_for_quiet must be false") + } + for i := 0; i < QuietMinutes; i++ { + r.minute(600_000_000) + } + if r.t.WaitingForQuiet() { + t.Fatal("10 samples are 9 minutes: still not a full window") + } + r.minute(600_000_000) + if !r.t.WaitingForQuiet() { + t.Fatal("a full busy window: waiting_for_quiet must be true") + } +}