fix(cluster): stop saying "waiting for spots" when 76 have arrived

An operator sent two screenshots: 76 LIVE in the counter, and the panel beside
it reading "Waiting for spots… Spots will appear as the cluster sends them."
Both his band and mode were locked to the rig — 20 m, SSB — so the filters were
doing exactly their job and the empty state was describing a different problem
entirely. He went looking for a connection fault.

It now says how many arrived, names every filter currently narrowing the list,
and offers one button to clear them all. The band and mode locks are named
first: they follow the rig rather than a click, so they are the two nobody
remembers switching on.

Also times the connection. He reports the first launch taking a while to
produce spots and a restart connecting instantly, which is the signature of a
slow name resolution rather than a slow node — the OS caches the answer, so the
second run skips it. The log now carries the dial duration and the delay to the
first spot, which separates that from a node that simply had nothing to say.
Hypothesis, not conclusion: the next log settles it.
This commit is contained in:
2026-08-15 23:35:56 +02:00
parent 8113557015
commit be0326c862
4 changed files with 90 additions and 4 deletions
+18
View File
@@ -369,10 +369,21 @@ func (s *session) runOnce() (time.Time, error) {
// failure surfaces as an error on Read — which is the only thing that ends
// a session below.
d := net.Dialer{Timeout: 10 * time.Second, KeepAlive: 30 * time.Second}
// TIMED, and reported. An operator sees "connected" and no spots for a
// minute on the first launch, then an instant connection when the program is
// restarted — which is the signature of a slow name resolution rather than a
// slow cluster (the OS caches the answer, so the second run skips it). The
// only way to tell that from a node that simply had nothing to say is to
// know how long the dial itself took.
dialStart := time.Now()
conn, err := d.Dial("tcp", addr)
if err != nil {
applog.Printf("cluster[%s] dial %s failed after %s: %v", s.cfg.Name, addr, time.Since(dialStart).Round(time.Millisecond), err)
return time.Time{}, fmt.Errorf("dial %s: %w", addr, err)
}
applog.Printf("cluster[%s] connected to %s in %s", s.cfg.Name, addr, time.Since(dialStart).Round(time.Millisecond))
linkUpAt := time.Now()
firstSpotLogged := false
s.mu.Lock()
s.conn = conn
s.mu.Unlock()
@@ -556,6 +567,13 @@ func (s *session) runOnce() (time.Time, error) {
}
s.mu.Unlock()
if s.onSpot != nil {
if !firstSpotLogged {
firstSpotLogged = true
// The gap between the socket opening and the first spot is the
// other half of the answer: a long dial is the network, a quick
// dial and a long silence is the node (or the login) instead.
applog.Printf("cluster[%s] first spot %s after connecting", s.cfg.Name, time.Since(linkUpAt).Round(time.Millisecond))
}
s.onSpot(spot)
}
}