{"record":{"id":"534a332d417ec07f","repo":"ginuerzh/gost","slug":"accpet-on-closed-listener","errorCode":null,"errorMessage":"accpet on closed listener","messagePattern":"accpet on closed listener","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"forward.go","lineNumber":771,"sourceCode":"\t\t\t} else {\n\t\t\t\ttempDelay *= 2\n\t\t\t}\n\t\t\tif max := 6 * time.Second; tempDelay > max {\n\t\t\t\ttempDelay = max\n\t\t\t}\n\t\t\tlog.Logf(\"[rudp] Accept error: %v; retrying in %v\", err, tempDelay)\n\t\t\ttime.Sleep(tempDelay)\n\t\t\tcontinue\n\t\t}\n\t\treturn\n\t}\n}\n\nfunc (l *udpRemoteForwardListener) Accept() (conn net.Conn, err error) {\n\tselect {\n\tcase conn = <-l.connChan:\n\tcase <-l.closed:\n\t\terr = errors.New(\"accpet on closed listener\")\n\t}\n\treturn\n}\n\nfunc (l *udpRemoteForwardListener) Addr() net.Addr {\n\treturn l.addr\n}\n\nfunc (l *udpRemoteForwardListener) Close() error {\n\tl.closeMux.Lock()\n\tdefer l.closeMux.Unlock()\n\n\tselect {\n\tcase <-l.closed:\n\t\treturn nil\n\tdefault:\n\t\tl.connMap.Range(func(k interface{}, v *udpServerConn) bool {\n\t\t\tv.Close()","sourceCodeStart":753,"sourceCodeEnd":789,"githubUrl":"https://github.com/ginuerzh/gost/blob/a33fdbf4c98034f4bfeeaea9868909822b9c526d/forward.go#L753-L789","documentation":"udpRemoteForwardListener.Accept returns this error when the listener has already been closed. The Accept select blocks on connChan for incoming forwarded UDP connections, but if the listener's closed channel fires first, it returns a plain error stating Accept was called on a closed listener. This mirrors net.Listener semantics where Accept after Close must return an error.","triggerScenarios":"Calling Accept() on a udpRemoteForwardListener after Close() has been invoked (or concurrently while another goroutine closes it), typically in an accept loop that keeps looping after shutdown.","commonSituations":"Port-forward shutdown races: the main goroutine closes the listener on config reload or exit while the accept loop is still blocked in Accept; goroutine-leak cleanup code closing listeners twice; accepting in a loop without checking listener health.","solutions":["Stop the accept loop when Accept returns this error — treat it as a normal shutdown signal, not a failure","Ensure Close() is called exactly once and only after the accept goroutine has exited (use sync.WaitGroup)","Guard the accept loop with a closed flag or context cancellation before calling Accept again","Fix the typo'd message aside, wrap the error with context so shutdown logs are distinguishable from real Accept failures"],"exampleFix":"// before\nfor {\n  c, err := l.Accept()\n  if err != nil { log.Fatal(err) }\n  go handle(c)\n}\n// after\nfor {\n  c, err := l.Accept()\n  if err != nil {\n    if strings.Contains(err.Error(), \"closed listener\") { return } // normal shutdown\n    log.Printf(\"accept: %v\", err); return\n  }\n  go handle(c)\n}","handlingStrategy":"try-catch","validationCode":"// Go has no pre-check; track listener lifecycle yourself\nif l.isClosed() { return errors.New(\"listener already closed, skipping Accept\") }","typeGuard":"func isListenerClosed(err error) bool { return err != nil && strings.Contains(err.Error(), \"closed listener\") }","tryCatchPattern":"conn, err := l.Accept()\nif err != nil {\n    if isListenerClosed(err) { return nil } // normal shutdown\n    return fmt.Errorf(\"udp forward accept: %w\", err)\n}","preventionTips":["Treat any error from Accept as terminal for the accept loop","Close listeners exactly once (sync.Once) and after accept goroutines exit","Use WaitGroups to join accept loops before process shutdown"],"tags":["udp","listener-closed","concurrency","port-forwarding"],"backgroundTag":"listener-use-after-close","analyzedSha":"a33fdbf4c98034f4bfeeaea9868909822b9c526d","analyzedAt":"2026-09-02T22:15:54.506Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}