From 2135fed6cf1f6b1e238c831695915fca7d3c8a35 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 9 Nov 2026 09:04:39 +0100 Subject: [PATCH] A node's bucket opens under its own deadline, so a per-node page is ended by its budget too (issue 1966) Review round 3: the open on the first key of a node ran on the listing's context, whose deadline is spent before the page's budget. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- node-tools/internal/bus/state.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/node-tools/internal/bus/state.go b/node-tools/internal/bus/state.go index 396fe5a..1911720 100644 --- a/node-tools/internal/bus/state.go +++ b/node-tools/internal/bus/state.go @@ -166,6 +166,7 @@ func (c *Conn) StatePage(module, name, after string, maxBytes int, budget time.D } // The bind and the listing have a StateTimeout of their own, and each get another: the page's time budget is what // ends a slow page (with Next), never a deadline spread over the whole of it. + // ctx covers the bind and the listing alone; every bus request inside the loop takes each(). ctx, cancel := context.WithTimeout(context.Background(), StateTimeout) defer cancel() each := func() (context.Context, context.CancelFunc) { @@ -186,7 +187,10 @@ func (c *Conn) StatePage(module, name, after string, maxBytes int, budget time.D } kv, bound := buckets[node] if !bound { - if kv, err = fam.Open(ctx, node); err != nil { + octx, ocancel := each() // the open is a bus request in the loop: its own deadline, never ctx's + kv, err = fam.Open(octx, node) + ocancel() + if err != nil { return nil, err } buckets[node] = kv