fix(ac): recover UDP handler panics - #1628
Conversation
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## main #1628 +/- ##
==========================================
+ Coverage 12.54% 12.60% +0.06%
==========================================
Files 96 96
Lines 14526 14540 +14
==========================================
+ Hits 1822 1833 +11
- Misses 12526 12528 +2
- Partials 178 179 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
✅ Looks Good - Code looks good
Focused, defensive fix that adds a top-level panic-recovery seam to the AC's input-driven UDP handler goroutines. I reviewed it against security, correctness, breaking-change, performance, and code-quality criteria and found no blocking issues.
What I verified
Correctness of WaitGroup ownership (the trickiest part):
HandleUdpACOperationshad itsdefer a.wg.Done()removed, and ownership moved to therunUDPHandlerwrapper.HandleUdpACOperationshas exactly one caller (recvMessageRoutineviarunUDPHandler), so there is no risk of a leakedAddor a doubleDone.a.wg.Add(1)is still called beforego, soWait()cannot race ahead of the spawn.
Defer ordering:
- In
runUDPHandler,recoverUDPHandleris deferred afterwg.Done(), so (LIFO) the panic is recovered before the WaitGroup is released. Shutdown correctly waits for even a panicking AOP handler, matching the PR's stated intent. - In
tcpTempAccessHandler/udpTempAccessHandler, the order is Close thenrecoverUDPHandlerthenwg.Done(); the panic is contained beforeDone(). tempConnTerminatorgets recovery but intentionally nowg.Done()— consistent with it being launched via a baregowith no matchingwg.Add().
Nil-safety:
recoverUDPHandlerguardsa != nil && a.config != nil && a.config.ACId != "", falling back to unknown. TheTestRecoverUDPHandlerNilConfigIsSafecase exercises this path directly.
Tests cover the four meaningful behaviors: panic recovery + wg release, happy path, and nil-config safety.
Minor, non-blocking observations
recvMessageRoutineitself (the dispatch loop) is not wrapped in recovery, so a panic there (rather than in a spawned handler) would still crash the process. This is outside the PR's stated scope (packet-handler goroutines, not lifecycle routines), and the loop only reads a channel and dispatches — the risky parsing happens inside the now-protectedHandleUdpACOperations— so risk is low. Worth a follow-up, not this PR.p := ppdinrecvMessageRoutineis slightly redundant now that the value is passed positionally, but it is harmless and arguably clearer.debug.Stack()in the error log is appropriate for an actionable trace; just noting it lands in error-level logs.
Nice, surgical port of the process-safety portion of the upstream change. Note: I could not run go test ./ac in this environment due to sandbox restrictions, but the PR reports go test / go test -race passing, and the code resolves cleanly against the referenced symbols.
Summary
Why
The server now contains per-message handler panics, but the access controller still launches packet-driven goroutines without a top-level recovery boundary. A single unexpected panic in AOP or temporary-access handling therefore terminates
nhp-acd, dropping every in-flight access operation on that controller.The recovery boundary turns that failure into one dropped request with an actionable stack trace. It does not hide ordinary returned errors and does not recover long-running lifecycle routines outside the packet-handler scope.
This ports the portable process-safety portion of LayerV nhp#1655 while intentionally omitting LayerV-specific CloudWatch alarms.
Impact
Malformed input or an internal handler bug can no longer crash the AC process through these packet-facing goroutines. Shutdown still waits for every in-flight AOP handler, including one that panics.
Validation
cd endpoints && go test ./accd endpoints && go test -race ./acgit diff --check