check / check (push) Canceled after 0s
The 5-second checkpoint ran in TRUNCATE mode, which waits up to the 5-second busy timeout for every open read while holding the database lock every batch write needs. It now runs in PASSIVE mode, which never waits. A WAL file over 64 MiB instead gets one TRUNCATE checkpoint whose wait for reads is capped at 500 ms and logged; it empties the file once no read is open, and while reads follow one another with no pause it can give up every time. A checkpoint that leaves part of the WAL uncopied returns ErrCheckpointIncomplete, which the maintainer logs as incomplete instead of completed. The writing connection's journal_size_limit is 64 MiB. Model: opus-5-5
55 lines
1.4 KiB
Go
55 lines
1.4 KiB
Go
package routewatch
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
|
)
|
|
|
|
// incompleteCheckpointStore is a store whose every checkpoint leaves part of
|
|
// the WAL uncopied.
|
|
type incompleteCheckpointStore struct{ *mockStore }
|
|
|
|
// Checkpoint reports the checkpoint incomplete.
|
|
func (incompleteCheckpointStore) Checkpoint(context.Context) error {
|
|
return database.ErrCheckpointIncomplete
|
|
}
|
|
|
|
// TestIncompleteCheckpointNotLoggedAsCompleted runs a checkpoint that leaves
|
|
// part of the WAL uncopied and checks that the maintainer logs it as
|
|
// incomplete, not as completed, and does not count it as a checkpoint that
|
|
// completed.
|
|
func TestIncompleteCheckpointNotLoggedAsCompleted(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var logs bytes.Buffer
|
|
|
|
logger := slog.New(slog.NewJSONHandler(&logs,
|
|
&slog.HandlerOptions{Level: slog.LevelDebug}))
|
|
m := NewDBMaintainer(incompleteCheckpointStore{newMockStore()}, logger)
|
|
|
|
m.runCheckpoint(t.Context())
|
|
|
|
var logged struct {
|
|
Msg string `json:"msg"`
|
|
}
|
|
|
|
err := json.Unmarshal(logs.Bytes(), &logged)
|
|
if err != nil {
|
|
t.Fatalf("want one log message, got %q: %v", logs.String(), err)
|
|
}
|
|
|
|
if logged.Msg != checkpointIncompleteMessage {
|
|
t.Errorf("logged %q, want %q", logged.Msg, checkpointIncompleteMessage)
|
|
}
|
|
|
|
if m.checkpointCount != 0 {
|
|
t.Errorf("checkpointCount = %d after an incomplete checkpoint, want 0",
|
|
m.checkpointCount)
|
|
}
|
|
}
|