Bound each shutdown stage by the stop deadline (closes #100)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Shutdown passes its context to every stage: the streamer, the four handlers, the WHOIS fetcher, the database maintainer and the HTTP server each stop waiting when it ends. A handler then skips its last write and logs how many updates it did not write, and the HTTP server closes the connections still open. The database is checkpointed and closed last, and left open once the deadline has passed, since a write may still hold it. The stop hook gives Shutdown 8 seconds and fx 9, inside the 10 seconds upaas and docker stop allow, so a stop signal exits 0. Model: opus-5-5
This commit was merged in pull request #130.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.eeqj.de/sneak/routewatch/internal/database"
|
||||
)
|
||||
@@ -52,3 +53,68 @@ func TestIncompleteCheckpointNotLoggedAsCompleted(t *testing.T) {
|
||||
m.checkpointCount)
|
||||
}
|
||||
}
|
||||
|
||||
// heldCheckpointStore is a store whose checkpoint closes started and then
|
||||
// waits until release is closed.
|
||||
type heldCheckpointStore struct {
|
||||
*mockStore
|
||||
|
||||
started chan struct{}
|
||||
release chan struct{}
|
||||
}
|
||||
|
||||
// Checkpoint waits until release is closed.
|
||||
func (s heldCheckpointStore) Checkpoint(context.Context) error {
|
||||
close(s.started)
|
||||
<-s.release
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestDBMaintainerStopReturnsByItsDeadline stops the maintainer with a short
|
||||
// deadline while its goroutine runs a checkpoint that the store holds until
|
||||
// the test ends. Stop must return all the same instead of waiting for the
|
||||
// checkpoint.
|
||||
func TestDBMaintainerStopReturnsByItsDeadline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const stopDeadline = 100 * time.Millisecond
|
||||
|
||||
store := heldCheckpointStore{
|
||||
mockStore: newMockStore(),
|
||||
started: make(chan struct{}),
|
||||
release: make(chan struct{}),
|
||||
}
|
||||
m := NewDBMaintainer(store, slog.Default())
|
||||
|
||||
// Start would run the first checkpoint only after checkpointInterval.
|
||||
// This goroutine stands for the one Start runs and checkpoints at once.
|
||||
go func() {
|
||||
defer close(m.done)
|
||||
|
||||
m.runCheckpoint(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-store.started:
|
||||
case <-time.After(handleMessageTimeout):
|
||||
t.Fatal("the maintainer did not start the checkpoint")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), stopDeadline)
|
||||
defer cancel()
|
||||
|
||||
if !returnsInTime(func() { m.Stop(ctx) }) {
|
||||
t.Fatalf("Stop with a deadline of %s did not return while a "+
|
||||
"checkpoint was in progress", stopDeadline)
|
||||
}
|
||||
|
||||
close(store.release)
|
||||
|
||||
select {
|
||||
case <-m.done:
|
||||
case <-time.After(handleMessageTimeout):
|
||||
t.Fatal("the maintenance goroutine did not return after the " +
|
||||
"checkpoint ended")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user