fix(coalesce): surface panic error to coalesced waiters

When the primary request panicked, waiters received a nil response
and nil error, which the proxy turned into an empty HTTP 200.

Recover in the deferred release, store an error response for the
waiters, then re-panic so the primary panic semantics are
unchanged. The test gates the panic on the live waiter count, so
every waiter is coalesced before the release.
This commit is contained in:
2026-08-14 00:01:31 +01:00
parent 2c2fe2623f
commit fcdf76b8e8
2 changed files with 118 additions and 0 deletions
+20
View File
@@ -1,6 +1,7 @@
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
@@ -142,7 +143,26 @@ func (rc *RequestCoalescer) Do(key string, fn func() (*CoalescedResponse, error)
// Without this, a panicking fn() leaves the key in the inflight map and
// the WaitGroup counter positive, so every coalesced waiter for that key
// blocks on wg.Wait() forever (permanent deadlock + leak).
//
// A panic also never reaches the "store the result" block below, which
// would otherwise leave inflight.response nil: waiters would then read
// (nil, nil) instead of an error. Recover here, record an error response
// for the waiters, release them, then re-panic so the primary caller's
// own panic semantics are unchanged.
defer func() {
if r := recover(); r != nil {
inflight.mu.Lock()
inflight.response = &CoalescedResponse{
Err: fmt.Errorf("coalesced request panicked: %v", r),
}
inflight.mu.Unlock()
rc.inflight.Delete(key)
inflight.wg.Done()
panic(r)
}
rc.inflight.Delete(key)
inflight.wg.Done()
}()