package cluster import ( "net" "strconv" "testing" "time" ) // A quiet cluster must not be treated as a broken one. // // Reported by an operator whose node sends little: nothing for two minutes and // OpsLog reconnected — losing the login, the filters, and any spot that landed // during the gap. The read deadline was being read as a verdict on the link // rather than as a way to stay responsive to a stop request. // // The test holds the connection open, silent for longer than one deadline, then // sends a spot. A session that survives receives it; one that reconnects does // not, because this listener never accepts twice. func TestSilenceDoesNotEndTheSession(t *testing.T) { // Shorten the tick so the test exercises the real code path in seconds. defer func(orig time.Duration) { idleTick = orig }(idleTick) idleTick = time.Second ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer ln.Close() accepted := make(chan net.Conn, 2) go func() { for { c, err := ln.Accept() if err != nil { return } accepted <- c } }() host, portStr, _ := net.SplitHostPort(ln.Addr().String()) port, err := strconv.Atoi(portStr) if err != nil { t.Fatal(err) } spots := make(chan Spot, 4) s := &session{ cfg: ServerConfig{Name: "quiet", Host: host, Port: port}, onSpot: func(sp Spot) { spots <- sp }, onLine: func(Line) {}, onStatus: func() {}, stopCh: make(chan struct{}), } done := make(chan error, 1) go func() { _, err := s.runOnce(); done <- err }() conn := <-accepted defer conn.Close() // Silent for longer than one deadline: the loop must ride through it. time.Sleep(3 * idleTick) select { case c := <-accepted: c.Close() t.Fatal("the session reconnected during the silence") case err := <-done: t.Fatalf("the session ended during the silence: %v", err) default: } // Now a spot: it proves the ORIGINAL connection is still the live one. if _, err := conn.Write([]byte("DX de F4BPO: 14074.0 OY1CT FT8 1234Z\r\n")); err != nil { t.Fatal(err) } select { case sp := <-spots: if sp.DXCall != "OY1CT" { t.Errorf("spot from the wrong station: %+v", sp) } case <-time.After(5 * time.Second): t.Fatal("the spot sent after the silence never arrived") } close(s.stopCh) <-done }