From 034aa199041b3a5dc08d177a10adbf026854147b Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 23 Oct 2026 20:22:58 +0200 Subject: [PATCH] fix(19.7): RA-IN-04 the non-reserving store fallback writes the record first For a store without Reserve (only a test wrapper reaches this; both shipped stores reserve), serveNew now calls Put right after the Count check and before the server is built, and a failed connect or initialize deletes the record again. The session is therefore never in the local map without its record, so a reaper pass in between cannot close it as expired. The cap of this fallback stays check-then-act, as its comment now says. --- modules/pier/session.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/pier/session.go b/modules/pier/session.go index 193fe6cf..57082a45 100644 --- a/modules/pier/session.go +++ b/modules/pier/session.go @@ -146,7 +146,11 @@ func (sr *sessionRouter) serveNew(w http.ResponseWriter, r *http.Request, id Ide sid := rand.Text() rec := Record{Binding: id.Binding, Created: time.Now().UTC()} // The slot is reserved atomically before the server is built - // (A-WR-03); a store without Reserve falls back to Count then Put. + // (A-WR-03). A store without Reserve (only a test wrapper; both shipped + // stores reserve) falls back to Count then Put, still before the server + // is built, so the session is never in sr.local without its record (a + // reaper pass would close it as expired); its cap stays check-then-act + // (RA-IN-04). res, reserves := sr.p.store.(reserver) if reserves { ok, err := res.Reserve(ctx, id.Binding, sid, rec, sr.p.cfg.MaxPerBinding, sr.p.cfg.IdleTTL) @@ -168,6 +172,10 @@ func (sr *sessionRouter) serveNew(w http.ResponseWriter, r *http.Request, id Ide sr.tooMany(w) return } + if err := sr.p.store.Put(ctx, sid, rec, sr.p.cfg.IdleTTL); err != nil { + sr.p.storeUnavailable(w, err) + return + } } release := func() { if reserves { @@ -189,13 +197,6 @@ func (sr *sessionRouter) serveNew(w http.ResponseWriter, r *http.Request, id Ide sr.mu.Lock() sr.local[sid] = ls sr.mu.Unlock() - if !reserves { - if err := sr.p.store.Put(ctx, sid, rec, sr.p.cfg.IdleTTL); err != nil { - sr.closeLocal(sid) - sr.p.storeUnavailable(w, err) - return - } - } logSession(logger(), "open", sid, id.Binding) ls.transport.ServeHTTP(w, r) if ls.ss.InitializeParams() == nil {