mesh/delivery held for a person: merged without a passing check: only a person decides what follows
mesh/check checking e9a885cda925 on 62802cd96602
At 16:07:36 the builder rewrote about 120 done keys at once. Each woke a take-in of its own with no limit, and each take-in read the rest of the mesh through restOfTheMeshOf, which opened a licences and an identity pool per call. novox's store ran out of connections at 16:08 and refused everyone, the controller's retries keeping it full. - The serving controller lends its stores by inventory (shareStores); restOfTheMeshOf reads on them instead of opening pools per call, and a verb run as its own process still opens its own. stores opens each store once under a lock, since readers now share it. serve uses the shared identity store instead of a second pool. - The watch's take-ins and the start's look share one cap of 16 take-ins at once (takeInSlots); a take-in waiting for its next try holds none. - store.Ready backs off on a full store (SQLSTATE 53300), doubling its wait up to 5 s, and says once that the store is full. Issues 1945 and 1277.
83 lines
2.6 KiB
Go
83 lines
2.6 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
)
|
|
|
|
// A full store (SQLSTATE 53300, "too many clients already") is not asked again every 250 ms: Ready backs off,
|
|
// doubling its wait up to a bound, and says once that the store is full (issues 1945 and 1277, 2026-10-25 16:08: the
|
|
// controller's retries kept novox's store full).
|
|
func TestReadyBacksOffOnAFullStore(t *testing.T) {
|
|
wasPing, wasWait, wasSay := pingStore, readyWait, readySay
|
|
t.Cleanup(func() { pingStore, readyWait, readySay = wasPing, wasWait, wasSay })
|
|
asked := 0
|
|
pingStore = func(context.Context, *Store) error {
|
|
asked++
|
|
if asked <= 8 {
|
|
return &pgconn.PgError{Code: "53300", Message: "sorry, too many clients already"}
|
|
}
|
|
return nil
|
|
}
|
|
var waits []time.Duration
|
|
readyWait = func(_ context.Context, d time.Duration) error { waits = append(waits, d); return nil }
|
|
var said []string
|
|
readySay = func(line string) { said = append(said, line) }
|
|
|
|
if err := (&Store{context: "example"}).Ready(context.Background(), time.Hour); err != nil {
|
|
t.Fatalf("Ready: %v", err)
|
|
}
|
|
if len(waits) != 8 {
|
|
t.Fatalf("waited %d times, want 8: %v", len(waits), waits)
|
|
}
|
|
for i := 1; i < len(waits); i++ {
|
|
if waits[i] < waits[i-1] {
|
|
t.Fatalf("wait %d (%s) is shorter than the one before (%s): %v", i, waits[i], waits[i-1], waits)
|
|
}
|
|
}
|
|
if waits[1] <= waits[0] {
|
|
t.Fatalf("Ready does not back off on a full store: %v", waits)
|
|
}
|
|
if most := waits[len(waits)-1]; most > fullStoreMostWait {
|
|
t.Fatalf("the wait grew past its bound %s: %v", fullStoreMostWait, waits)
|
|
}
|
|
full := 0
|
|
for _, l := range said {
|
|
if strings.Contains(l, "full") {
|
|
full++
|
|
}
|
|
}
|
|
if full != 1 {
|
|
t.Fatalf("said that the store is full %d times, want once: %q", full, said)
|
|
}
|
|
}
|
|
|
|
// A store that does not answer for another reason (not up yet) is still asked again every 250 ms.
|
|
func TestReadyWaitsShortlyOnAStoreNotUp(t *testing.T) {
|
|
wasPing, wasWait, wasSay := pingStore, readyWait, readySay
|
|
t.Cleanup(func() { pingStore, readyWait, readySay = wasPing, wasWait, wasSay })
|
|
asked := 0
|
|
pingStore = func(context.Context, *Store) error {
|
|
asked++
|
|
if asked <= 3 {
|
|
return context.DeadlineExceeded
|
|
}
|
|
return nil
|
|
}
|
|
var waits []time.Duration
|
|
readyWait = func(_ context.Context, d time.Duration) error { waits = append(waits, d); return nil }
|
|
readySay = func(string) {}
|
|
if err := (&Store{context: "example"}).Ready(context.Background(), time.Hour); err != nil {
|
|
t.Fatalf("Ready: %v", err)
|
|
}
|
|
for _, w := range waits {
|
|
if w != 250*time.Millisecond {
|
|
t.Fatalf("waits %v, want 250ms each", waits)
|
|
}
|
|
}
|
|
}
|