{"record":{"id":"74c2b245f5cc0e2a","repo":"OpenNHP/opennhp","slug":"duplicate-in-flight-counter","errorCode":null,"errorMessage":"duplicate in-flight counter","messagePattern":"duplicate in-flight counter","errorType":"http","errorClass":null,"httpStatus":409,"severity":"error","filePath":"endpoints/relay/relay.go","lineNumber":1046,"sourceCode":"\n\tlog.Info(\"[Relay] forwarding %d-byte inner packet (counter=%d, server=%s) from client %s to %s (sticky=%v)\",\n\t\tn, innerCounter, cr.id, realAddr, inst.addr, cr.sticky)\n\n\t// Register a pending request under (counter, realAddr) on the instance.\n\t// The connection routine dispatches the server's ACK/COK to this channel\n\t// only if this handler is the sole waiter on this counter — see the\n\t// ambiguity check in connectionRoutine above.\n\tresponseCh := make(chan []byte, 1)\n\tinst.pendingMu.Lock()\n\twaiters, ok := inst.pendingRequests[innerCounter]\n\tif !ok {\n\t\twaiters = make(map[string]chan []byte)\n\t\tinst.pendingRequests[innerCounter] = waiters\n\t}\n\tif _, dup := waiters[realAddrKey]; dup {\n\t\t// Same client reusing the same counter concurrently — reject fast.\n\t\tinst.pendingMu.Unlock()\n\t\thttp.Error(w, \"duplicate in-flight counter\", http.StatusConflict)\n\t\treturn\n\t}\n\twaiters[realAddrKey] = responseCh\n\tinst.pendingMu.Unlock()\n\n\t// Ensure cleanup on timeout / early return.\n\tdefer func() {\n\t\tinst.pendingMu.Lock()\n\t\tif waiters, ok := inst.pendingRequests[innerCounter]; ok {\n\t\t\tdelete(waiters, realAddrKey)\n\t\t\tif len(waiters) == 0 {\n\t\t\t\tdelete(inst.pendingRequests, innerCounter)\n\t\t\t}\n\t\t}\n\t\tinst.pendingMu.Unlock()\n\t}()\n\n\t// Construct RelayForwardMsg (standard JSON body).","sourceCodeStart":1028,"sourceCodeEnd":1064,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/relay.go#L1028-L1064","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: retry reuses the same packet/counter\nfor try := 0; try < 3; try++ { send(packet) }\n// after\nfor try := 0; try < 3; try++ {\n    packet = packetWithNewCounter() // fresh counter at [16:24]\n    send(packet)\n}","handlingStrategy":"validation","validationCode":"// always mint a fresh counter before each send\nbinary.BigEndian.PutUint64(header[16:24], nextCounter())","typeGuard":null,"tryCatchPattern":"if resp.StatusCode == http.StatusConflict {\n    b, _ := io.ReadAll(resp.Body)\n    if strings.Contains(string(b), \"duplicate in-flight counter\") {\n        return errors.New(\"counter reuse detected; rebuild packet with a new counter instead of retrying the same bytes\")\n    }\n}","preventionTips":["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"],"tags":["relay","concurrency","counter-collision"],"backgroundTag":"conflicting-config-options","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}