classifyReturn maps a parser's return (panic or error or nil) plus the supervisor's sticky abort flags to a Result.
(outerCtx context.Context, panicked bool, panicVal any, stack []byte, fnErr error)
| 308 | // classifyReturn maps a parser's return (panic or error or nil) plus |
| 309 | // the supervisor's sticky abort flags to a Result. |
| 310 | func (s *Supervisor) classifyReturn(outerCtx context.Context, panicked bool, panicVal any, stack []byte, fnErr error) Result { |
| 311 | if panicked { |
| 312 | s.cfg.Logger.Error("parser panicked", |
| 313 | zap.Any("panic", panicVal), |
| 314 | zap.ByteString("stack", stack), |
| 315 | zap.String("next_step", "the supervisor is falling through to raw passthrough so user traffic continues unaffected; file the panic with the parser owner using the captured stack, and set KEPLOY_NEW_RELAY=off to force the legacy path for this parser, or KEPLOY_DISABLE_PARSING=1 / SIGUSR1 to disable parser dispatch entirely until the root cause is fixed"), |
| 316 | ) |
| 317 | s.reportPanic(panicVal, stack) |
| 318 | s.fireOnAbort() |
| 319 | return Result{ |
| 320 | Status: StatusPanicked, |
| 321 | Err: wrapPanic(panicVal), |
| 322 | FallthroughToPassthrough: true, |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | // Sticky flags beat a "clean" return: if we already declared |
| 327 | // the parser dead and it happened to return the exact moment we |
| 328 | // cancelled it, surface the real reason. |
| 329 | if s.memCapExceeded.Load() { |
| 330 | return Result{ |
| 331 | Status: StatusMemCap, |
| 332 | Err: fnErr, |
| 333 | FallthroughToPassthrough: true, |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | if fnErr == nil { |
| 338 | return Result{Status: StatusOK} |
| 339 | } |
| 340 | if errors.Is(fnErr, context.Canceled) && outerCtx.Err() != nil { |
| 341 | return Result{Status: StatusCanceled, Err: fnErr} |
| 342 | } |
| 343 | // G1 fix: a parser that returned a non-nil error on its own |
| 344 | // (decode failure, malformed wire frame, decompression error, |
| 345 | // etc.) is in the same situation as a panic from the user-traffic |
| 346 | // perspective — the bytes have already been forwarded by the |
| 347 | // relay, and the parser's failure to record a clean mock has no |
| 348 | // bearing on whether the application's connection should survive. |
| 349 | // Set FallthroughToPassthrough so the dispatcher leaves the relay |
| 350 | // alone and bytes keep flowing until peer close. fireOnAbort is |
| 351 | // invoked so the SessionOnAbort callback can pause the tees and |
| 352 | // close the FakeConns; without it the tees keep accumulating |
| 353 | // chunks for a parser that will never read them, eventually |
| 354 | // dropping at DropChannelFull and spamming Debug logs. |
| 355 | s.fireOnAbort() |
| 356 | return Result{ |
| 357 | Status: StatusError, |
| 358 | Err: fnErr, |
| 359 | FallthroughToPassthrough: true, |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | // reportPanic invokes cfg.PanicReporter guarded against reporter |
| 364 | // panics, so a buggy reporter cannot turn a recovered parser panic |
no test coverage detected