check / check (push) Failing after 1s
The server ran os.Exit at the end of its own goroutine, racing fx's teardown and sometimes killing the process before reportbuf's OnStop flushed — silently losing a full flush window of telemetry on every restart, at exit 0. Shutdown now goes through fx.Shutdowner, so every OnStop runs in order. The http.Server is built synchronously in OnStart before the serving goroutine, so shutdown can no longer race or nil-deref it. A listen failure exits non-zero via fx.ExitCode(1). reportbuf's OnStop is guarded by sync.Once. writeTimeout now exceeds the chi per-request budget so that budget is reachable. Dead startupTime, exitCode, and cancelFunc fields are gone. A new test asserts a buffered report reaches disk after the lifecycle stops. Model: opus-4-8
78 lines
1.6 KiB
Go
78 lines
1.6 KiB
Go
package reportbuf_test
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/netwatch/internal/config"
|
|
"sneak.berlin/go/netwatch/internal/globals"
|
|
"sneak.berlin/go/netwatch/internal/logger"
|
|
"sneak.berlin/go/netwatch/internal/reportbuf"
|
|
|
|
"go.uber.org/fx"
|
|
"go.uber.org/fx/fxtest"
|
|
)
|
|
|
|
// TestFlushOnShutdown proves the flush-on-shutdown path: a
|
|
// report appended after start but before the periodic flush
|
|
// window must reach disk when the fx lifecycle stops. This is
|
|
// the exact case that silent data loss on restart used to
|
|
// destroy.
|
|
func TestFlushOnShutdown(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("DATA_DIR", dir)
|
|
|
|
var buf *reportbuf.Buffer
|
|
|
|
app := fxtest.New(t,
|
|
fx.Provide(
|
|
globals.New,
|
|
logger.New,
|
|
config.New,
|
|
reportbuf.New,
|
|
),
|
|
fx.Populate(&buf),
|
|
)
|
|
|
|
app.RequireStart()
|
|
|
|
err := buf.Append(map[string]string{"probe": "shutdown"})
|
|
if err != nil {
|
|
t.Fatalf("append report: %v", err)
|
|
}
|
|
|
|
// RequireStop runs the reportbuf OnStop hook, which is the
|
|
// only code path that flushes buffered reports on shutdown.
|
|
app.RequireStop()
|
|
|
|
if !hasReportFile(t, dir) {
|
|
t.Fatal("no report file on disk after shutdown; " +
|
|
"the buffered report was lost")
|
|
}
|
|
}
|
|
|
|
func hasReportFile(t *testing.T, dir string) bool {
|
|
t.Helper()
|
|
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
t.Fatalf("read data dir: %v", err)
|
|
}
|
|
|
|
for _, e := range entries {
|
|
if strings.HasSuffix(e.Name(), ".jsonl.zst") {
|
|
info, statErr := e.Info()
|
|
if statErr != nil {
|
|
t.Fatalf("stat %s: %v", e.Name(), statErr)
|
|
}
|
|
|
|
if info.Size() > 0 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|