{"record":{"id":"7b897457b9841d1d","repo":"fatedier/frp","slug":"listener-closed-7b8974","errorCode":null,"errorMessage":"listener closed","messagePattern":"listener closed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"info","filePath":"pkg/util/vhost/vhost.go","lineNumber":242,"sourceCode":"}\n\ntype Listener struct {\n\tname            string\n\tlocation        string\n\trouteByHTTPUser string\n\trewriteHost     string\n\tusername        string\n\tpassword        string\n\tmux             *Muxer // for closing Muxer\n\taccept          chan net.Conn\n\tctx             context.Context\n}\n\nfunc (l *Listener) Accept() (net.Conn, error) {\n\txl := xlog.FromContextSafe(l.ctx)\n\tconn, ok := <-l.accept\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"listener closed\")\n\t}\n\n\t// if rewriteHost func is exist\n\t// rewrite http requests with a modified host header\n\t// if l.rewriteHost is empty, nothing to do\n\tif l.mux.rewriteHost != nil {\n\t\tsConn, err := l.mux.rewriteHost(conn, l.rewriteHost)\n\t\tif err != nil {\n\t\t\txl.Warnf(\"host header rewrite failed: %v\", err)\n\t\t\treturn nil, fmt.Errorf(\"host header rewrite failed\")\n\t\t}\n\t\txl.Debugf(\"rewrite host to [%s] success\", l.rewriteHost)\n\t\tconn = sConn\n\t}\n\treturn netpkg.NewContextConn(l.ctx, conn), nil\n}\n\nfunc (l *Listener) Close() error {","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/util/vhost/vhost.go#L224-L260","documentation":"Returned by frp's vhost Listener.Accept when the internal accept channel has been closed — i.e., the virtual-host listener was closed (Listener.Close closes l.accept, or the owning Muxer shut down). It plays the role of net.ErrClosed for vhost listeners and tells the accept loop to exit.","triggerScenarios":"Calling Accept() on a vhost Listener after Close(); the muxer closing all listener channels during frps shutdown or proxy unregistration while a ServeHTTP-style accept loop is still blocked in Accept.","commonSituations":"frps reload/shutdown dropping an http proxy while its handler goroutine is still accepting; code that wraps vhost.Listener in http.Server and closes it on config reload. Seeing this during graceful shutdown is normal.","solutions":["Treat as shutdown: exit the accept loop and release per-conn resources; do not retry Accept","On reload, close listeners only after their accept loops have been signaled to stop (coordinate via context/WaitGroup)","If seen repeatedly with frps still running, check for a proxy name collision or registry error closing the listener early"],"exampleFix":"// before\nfor {\n    conn, err := vl.Accept()\n    if err != nil { panic(err) }\n}\n\n// after\nfor {\n    conn, err := vl.Accept()\n    if err != nil { break } // listener closed -> stop serving\n    go handle(conn)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"conn, err := vlistener.Accept()\nif err != nil {\n    if strings.Contains(err.Error(), \"listener closed\") {\n        return nil // shutdown: propagate as clean stop\n    }\n    return err\n}","preventionTips":["Wrap vhost listeners so their accept loops exit on context cancellation before Close","On hot reload, sequence: stop accept loop, then Close listener, then re-register","Don't restart Accept in a retry loop — closure is permanent"],"tags":["vhost","network","lifecycle","shutdown"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}