{"record":{"id":"e88a9668bf2faa0f","repo":"ginuerzh/gost","slug":"accpet-on-closed-listener-e88a96","errorCode":null,"errorMessage":"accpet on closed listener","messagePattern":"accpet on closed listener","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"ftcp.go","lineNumber":139,"sourceCode":"\n\t\tselect {\n\t\tcase conn.rChan <- b[:n]:\n\t\t\tif Debug {\n\t\t\t\tlog.Logf(\"[ftcp] %s >>> %s : length %d\", raddr, l.Addr(), n)\n\t\t\t}\n\t\tdefault:\n\t\t\tlog.Logf(\"[ftcp] %s -> %s : recv queue is full (%d)\", raddr, l.Addr(), cap(conn.rChan))\n\t\t}\n\t}\n}\n\nfunc (l *fakeTCPListener) Accept() (conn net.Conn, err error) {\n\tvar ok bool\n\tselect {\n\tcase conn = <-l.connChan:\n\tcase err, ok = <-l.errChan:\n\t\tif !ok {\n\t\t\terr = errors.New(\"accpet on closed listener\")\n\t\t}\n\t}\n\treturn\n}\n\nfunc (l *fakeTCPListener) Addr() net.Addr {\n\treturn l.ln.LocalAddr()\n}\n\nfunc (l *fakeTCPListener) Close() error {\n\terr := l.ln.Close()\n\tl.connMap.Range(func(k interface{}, v *udpServerConn) bool {\n\t\tv.Close()\n\t\treturn true\n\t})\n\n\treturn err\n}","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/ginuerzh/gost/blob/a33fdbf4c98034f4bfeeaea9868909822b9c526d/ftcp.go#L121-L157","documentation":"fakeTCPListener.Accept returns this error when the listener's errChan is closed, which happens when the listener has been closed. Accept selects between receiving a connection from connChan and an error from errChan; a closed errChan (ok == false) means the listener is shut down and no further connections will arrive. It is the fakeTCP (relay-over-UDP) equivalent of the standard 'use of closed network connection' error.","triggerScenarios":"Calling Accept() on a fakeTCPListener after Close() closed errChan, or racing with Close() during shutdown while an accept loop is still running.","commonSituations":"Relay service shutdown while worker goroutines still sit in Accept; double-close of the listener from both a timeout handler and an explicit Stop; accept loops that treat every Accept error as retryable and spin on the closed channel.","solutions":["Exit the accept loop when this error is returned — after listener close it will be returned forever","Coordinate shutdown: signal the accept goroutine to stop before/when calling Close()","Avoid double-closing the listener (use sync.Once around Close)","Log it at debug level; it is expected during orderly shutdown"],"exampleFix":"// before\nc, err := ln.Accept()\nif err != nil { return fmt.Errorf(\"accept: %w\", err) }\n// after\nc, err := ln.Accept()\nif err != nil {\n  if strings.Contains(err.Error(), \"closed listener\") { return nil } // listener closed, stop loop\n  return fmt.Errorf(\"accept: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"// Track shutdown state explicitly before looping\nselect {\ncase <-ln.closedChan:\n    return // do not call Accept\ndefault:\n}","typeGuard":"func isListenerClosedErr(err error) bool { return err != nil && strings.Contains(err.Error(), \"closed listener\") }","tryCatchPattern":"conn, err := ln.Accept()\nif err != nil {\n    if isListenerClosedErr(err) { return nil } // expected at shutdown\n    return fmt.Errorf(\"fakeTCP accept: %w\", err)\n}","preventionTips":["Break out of the accept loop on the first Accept error","Guard Close with sync.Once to avoid double-close racing Accept","Signal accept goroutines to stop before calling Close"],"tags":["tcp","listener-closed","concurrency","relay"],"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"}