Compare commits
1
Commits
next
..
cddae7fd68
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cddae7fd68 |
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
- branch (from `main`)
|
- branch (from `main`)
|
||||||
- do the work in Next Step
|
- do the work in Next Step
|
||||||
- delete Next Step from `TODO.md`
|
- move Next Step to the top of Completed Steps
|
||||||
- move the top item of Future Steps into Next Step
|
- move the top item of Future Steps into Next Step
|
||||||
- commit (`TODO.md` changes in the same commit as the work)
|
- commit (`TODO.md` changes in the same commit as the work)
|
||||||
- merge to `main` if the branch is not protected, otherwise open a PR
|
- merge to `main` if the branch is not protected, otherwise open a PR
|
||||||
@@ -16,14 +16,10 @@ https://git.eeqj.de/sneak/routewatch/issues/86.
|
|||||||
# Next Step
|
# Next Step
|
||||||
|
|
||||||
Post the outcome of the run of `e58c663` on the live feed on
|
Post the outcome of the run of `e58c663` on the live feed on
|
||||||
https://git.eeqj.de/sneak/routewatch/issues/232. Work it out from the run's
|
https://git.eeqj.de/sneak/routewatch/issues/232. The run began at 09:19 UTC on
|
||||||
data:
|
2026-11-10 and lasts at least 24 hours. If the feed has not stalled or
|
||||||
|
reconnected by then, the run goes on until it does, up to 72 hours in all. The
|
||||||
- the log of the stopped container `routewatch-run-e58c663`, read with
|
outcome says:
|
||||||
`docker logs`
|
|
||||||
- the database in the Docker volume `routewatch-run-e58c663`
|
|
||||||
|
|
||||||
Keep the container until the outcome is posted. The outcome says:
|
|
||||||
|
|
||||||
- whether the feed stalled or reconnected
|
- whether the feed stalled or reconnected
|
||||||
- whether any handler dropped an update, and the most messages the prefix
|
- whether any handler dropped an update, and the most messages the prefix
|
||||||
@@ -32,9 +28,6 @@ Keep the container until the outcome is posted. The outcome says:
|
|||||||
- the size of the `origin_changes` table on disk
|
- the size of the `origin_changes` table on disk
|
||||||
- the process's peak memory against 5 GiB
|
- the process's peak memory against 5 GiB
|
||||||
|
|
||||||
The run's data does not show the most messages the prefix handler held back at
|
File an issue for each problem the run finds.
|
||||||
once, or the process's peak memory.
|
|
||||||
|
|
||||||
File an issue for each problem the run found.
|
|
||||||
|
|
||||||
# Future Steps
|
# Future Steps
|
||||||
@@ -90,12 +90,14 @@ func parseAddresses(t *testing.T, addresses ...string) []netip.Addr {
|
|||||||
return addrs
|
return addrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// fakeGeoJS is a geojs server. It sends body, or status when that is set. It
|
// fakeGeoJS is a geojs server. It sends body, or status when that is set, or
|
||||||
// keeps each request's address list.
|
// waits for the request to end when hang is true. It keeps each request's
|
||||||
|
// address list.
|
||||||
type fakeGeoJS struct {
|
type fakeGeoJS struct {
|
||||||
server *httptest.Server
|
server *httptest.Server
|
||||||
body string
|
body string
|
||||||
status int
|
status int
|
||||||
|
hang bool
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
requests []string
|
requests []string
|
||||||
@@ -117,6 +119,12 @@ func (f *fakeGeoJS) serve(w http.ResponseWriter, r *http.Request) {
|
|||||||
f.requests = append(f.requests, r.URL.Query().Get("ip"))
|
f.requests = append(f.requests, r.URL.Query().Get("ip"))
|
||||||
f.mu.Unlock()
|
f.mu.Unlock()
|
||||||
|
|
||||||
|
if f.hang {
|
||||||
|
<-r.Context().Done()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if f.status != 0 {
|
if f.status != 0 {
|
||||||
w.WriteHeader(f.status)
|
w.WriteHeader(f.status)
|
||||||
|
|
||||||
@@ -134,20 +142,6 @@ func (f *fakeGeoJS) requestsSeen() []string {
|
|||||||
return append([]string{}, f.requests...)
|
return append([]string{}, f.requests...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hangingTransport never answers: each request waits until its context ends.
|
|
||||||
// It counts the requests as they are sent, so the count does not depend on
|
|
||||||
// whether one reaches a server before its time limit runs out.
|
|
||||||
type hangingTransport struct {
|
|
||||||
requests atomic.Int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hangingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
h.requests.Add(1)
|
|
||||||
<-r.Context().Done()
|
|
||||||
|
|
||||||
return nil, r.Context().Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// openCacheDB opens a new cache.db in a temporary directory.
|
// openCacheDB opens a new cache.db in a temporary directory.
|
||||||
func openCacheDB(t *testing.T, dir string) *sql.DB {
|
func openCacheDB(t *testing.T, dir string) *sql.DB {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -391,15 +385,13 @@ func TestLookupFallsBackToTheDatabase(t *testing.T) {
|
|||||||
|
|
||||||
fake := newFakeGeoJS(t, tt.body)
|
fake := newFakeGeoJS(t, tt.body)
|
||||||
fake.status = tt.status
|
fake.status = tt.status
|
||||||
|
fake.hang = tt.hang
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
locator := newTestLocator(t, fake, openCacheDB(t, dir), dir)
|
locator := newTestLocator(t, fake, openCacheDB(t, dir), dir)
|
||||||
requestCount := func() int { return len(fake.requestsSeen()) }
|
locator.timeLimit = shortTimeLimit
|
||||||
|
|
||||||
if tt.hang {
|
if !tt.hang {
|
||||||
hanging := &hangingTransport{}
|
locator.timeLimit = testTimeLimit
|
||||||
locator.client = &http.Client{Transport: hanging}
|
|
||||||
locator.timeLimit = shortTimeLimit
|
|
||||||
requestCount = func() int { return int(hanging.requests.Load()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
giveDatabase(t, locator)
|
giveDatabase(t, locator)
|
||||||
@@ -421,8 +413,8 @@ func TestLookupFallsBackToTheDatabase(t *testing.T) {
|
|||||||
// asks geojs again.
|
// asks geojs again.
|
||||||
lookupOne(t, locator, testAddress)
|
lookupOne(t, locator, testAddress)
|
||||||
|
|
||||||
if requests := requestCount(); requests != 2 {
|
if requests := fake.requestsSeen(); len(requests) != 2 {
|
||||||
t.Errorf("%d requests, want 2", requests)
|
t.Errorf("%d requests, want 2", len(requests))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,66 +275,49 @@ func TestPagesServeTheCopyOfTheCards(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestKeepHomeCardsFreshWorksThemOutEveryInterval runs keepHomeCardsFresh. It
|
// TestKeepHomeCardsFreshWorksThemOutEveryInterval runs keepHomeCardsFresh with
|
||||||
// works the cards out at once, again an interval after the table changes, and
|
// a short interval. It works the cards out at once, again after the table
|
||||||
// stops when its context ends. It runs in a synctest bubble, whose clock is
|
// changes, and stops when its context ends.
|
||||||
// fake, so a stall on the host cannot fail it.
|
|
||||||
func TestKeepHomeCardsFreshWorksThemOutEveryInterval(t *testing.T) {
|
func TestKeepHomeCardsFreshWorksThemOutEveryInterval(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const interval = time.Minute
|
const (
|
||||||
|
interval = 10 * time.Millisecond
|
||||||
|
maxWait = 15 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
table := emptyTable()
|
table := emptyTable()
|
||||||
announce(table, "1.0.0.0/24", twoPrefixASN, testPeer(1), testRouteTime(0))
|
announce(table, "1.0.0.0/24", twoPrefixASN, testPeer(1), testRouteTime(0))
|
||||||
|
|
||||||
s := newServerWithTable(t, table)
|
s := newServerWithTable(t, table)
|
||||||
|
|
||||||
// newestRouteOrigin returns the origin AS of the newest route on the
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
// cards, or 0 when there is none.
|
stopped := make(chan struct{})
|
||||||
newestRouteOrigin := func() int {
|
|
||||||
cards := s.cards.Load()
|
|
||||||
if cards == nil || cards.NewestRoute == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return cards.NewestRoute.OriginASN
|
go func() {
|
||||||
|
s.keepHomeCardsFresh(ctx, interval)
|
||||||
|
close(stopped)
|
||||||
|
}()
|
||||||
|
|
||||||
|
newestRouteASNIs := func(asn int) bool {
|
||||||
|
cards := s.cards.Load()
|
||||||
|
|
||||||
|
return cards != nil && cards.NewestRoute != nil &&
|
||||||
|
cards.NewestRoute.OriginASN == asn
|
||||||
}
|
}
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
waitUntil(t, maxWait, func() bool { return newestRouteASNIs(twoPrefixASN) })
|
||||||
ctx, cancel := context.WithCancel(t.Context())
|
|
||||||
stopped := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
announce(table, "8.8.8.0/24", newestRouteASN, testPeer(2), testRouteTime(1))
|
||||||
s.keepHomeCardsFresh(ctx, interval)
|
waitUntil(t, maxWait, func() bool { return newestRouteASNIs(newestRouteASN) })
|
||||||
close(stopped)
|
|
||||||
}()
|
|
||||||
|
|
||||||
synctest.Wait()
|
cancel()
|
||||||
|
|
||||||
if got := newestRouteOrigin(); got != twoPrefixASN {
|
select {
|
||||||
t.Fatalf("at the start, the newest route's AS is %d, want %d",
|
case <-stopped:
|
||||||
got, twoPrefixASN)
|
case <-time.After(maxWait):
|
||||||
}
|
t.Fatal("keepHomeCardsFresh did not stop when its context ended")
|
||||||
|
}
|
||||||
announce(table, "8.8.8.0/24", newestRouteASN, testPeer(2),
|
|
||||||
testRouteTime(1))
|
|
||||||
time.Sleep(interval)
|
|
||||||
synctest.Wait()
|
|
||||||
|
|
||||||
if got := newestRouteOrigin(); got != newestRouteASN {
|
|
||||||
t.Fatalf("an interval after the table changed, the newest "+
|
|
||||||
"route's AS is %d, want %d", got, newestRouteASN)
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel()
|
|
||||||
synctest.Wait()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-stopped:
|
|
||||||
default:
|
|
||||||
t.Fatal("keepHomeCardsFresh did not stop when its context ended")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestStartedServerWorksOutTheCards starts a server and waits until its home
|
// TestStartedServerWorksOutTheCards starts a server and waits until its home
|
||||||
|
|||||||
Reference in new issue
Block a user