OpenNHP/opennhp · error
duplicate in-flight counter
Error message
duplicate in-flight counter
What it means
handleRelay responds with HTTP 409 'duplicate in-flight counter' when the same client (identified by realAddrKey) already has a waiter registered for the same inner packet counter. Since responses are matched to requests by (instance, counter, client), a concurrent duplicate would be ambiguous, so the relay rejects it fast while holding pendingMu only briefly.
Solutions
- Regenerate a fresh counter (NextCounterIndex) for each request before sending
- Disable automatic transport-level retries, or make retries rebuild the packet with a new counter
- Serialize requests per client, or wait for the in-flight response before reusing a counter
- Verify the client increments the counter at bytes [16:24] on every send
Example fix
// before: retry reuses the same packet/counter
for try := 0; try < 3; try++ { send(packet) }
// after
for try := 0; try < 3; try++ {
packet = packetWithNewCounter() // fresh counter at [16:24]
send(packet)
} Defensive patterns
Strategy: validation
Validate before calling
// always mint a fresh counter before each send binary.BigEndian.PutUint64(header[16:24], nextCounter())
Try / catch
if resp.StatusCode == http.StatusConflict {
b, _ := io.ReadAll(resp.Body)
if strings.Contains(string(b), "duplicate in-flight counter") {
return errors.New("counter reuse detected; rebuild packet with a new counter instead of retrying the same bytes")
}
} Prevention
- Never reuse an in-flight counter; increment per request
- Disable blind transport retries that replay the identical body
- Serialize per-client requests or key retries on new counters
- Keep client counter state atomic across concurrent workers
When it happens
Trigger: POSTing two requests concurrently with identical inner packet counters from the same client IP — e.g. a client retrying with the same serialized packet before the first response arrives, or a buggy client that never increments its packet counter.
Common situations: HTTP client timeout/retry at the transport layer resending the exact same body; client code reusing a cached/stale packet with a fixed counter; parallel workers sharing one packet buffer.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- relay: failed to parse config
- relay: privateKeyBase64 must be set in config
- relay: no upstream configured; add at least one [[Servers]]…
- relay: server # missing publicKeyBase64
- relay: server # publicKeyBase64 invalid
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/74c2b245f5cc0e2a.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/relay/relay.go:1046
log.Info("[Relay] forwarding %d-byte inner packet (counter=%d, server=%s) from client %s to %s (sticky=%v)",
n, innerCounter, cr.id, realAddr, inst.addr, cr.sticky)
// Register a pending request under (counter, realAddr) on the instance.
// The connection routine dispatches the server's ACK/COK to this channel
// only if this handler is the sole waiter on this counter — see the
// ambiguity check in connectionRoutine above.
responseCh := make(chan []byte, 1)
inst.pendingMu.Lock()
waiters, ok := inst.pendingRequests[innerCounter]
if !ok {
waiters = make(map[string]chan []byte)
inst.pendingRequests[innerCounter] = waiters
}
if _, dup := waiters[realAddrKey]; dup {
// Same client reusing the same counter concurrently — reject fast.
inst.pendingMu.Unlock()
http.Error(w, "duplicate in-flight counter", http.StatusConflict)
return
}
waiters[realAddrKey] = responseCh
inst.pendingMu.Unlock()
// Ensure cleanup on timeout / early return.
defer func() {
inst.pendingMu.Lock()
if waiters, ok := inst.pendingRequests[innerCounter]; ok {
delete(waiters, realAddrKey)
if len(waiters) == 0 {
delete(inst.pendingRequests, innerCounter)
}
}
inst.pendingMu.Unlock()
}()
// Construct RelayForwardMsg (standard JSON body).View on GitHub (pinned to 6e04ca5ff0)