{"record":{"id":"1285e6b14a2f6098","repo":"fatedier/frp","slug":"channel-for-udp-listener-closed","errorCode":null,"errorMessage":"channel for udp listener closed","messagePattern":"channel for udp listener closed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"info","filePath":"pkg/util/net/udp.go","lineNumber":241,"sourceCode":"\t}()\n\tl.writeCh <- packet\n\treturn\n}\n\nfunc (l *UDPListener) WriteMsg(buf []byte, remoteAddr *net.UDPAddr) (err error) {\n\t// only set remote addr here\n\tpacket := &UDPPacket{\n\t\tBuf:        buf,\n\t\tRemoteAddr: remoteAddr,\n\t}\n\terr = l.writeUDPPacket(packet)\n\treturn\n}\n\nfunc (l *UDPListener) Accept() (net.Conn, error) {\n\tconn, ok := <-l.acceptCh\n\tif !ok {\n\t\treturn conn, fmt.Errorf(\"channel for udp listener closed\")\n\t}\n\treturn conn, nil\n}\n\nfunc (l *UDPListener) Close() error {\n\tif !l.closeFlag {\n\t\tl.closeFlag = true\n\t\tif l.readConn != nil {\n\t\t\tl.readConn.Close()\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (l *UDPListener) Addr() net.Addr {\n\treturn l.addr\n}\n","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/util/net/udp.go#L223-L259","documentation":"Returned by UDPListener.Accept when the internal accept channel has been closed. Accept models itself after net.Listener, but connections arrive via a channel; once the listener is closed and the channel is closed, a receive yields ok==false and this error is produced. It is the normal 'listener is shutting down' signal, equivalent to net.ErrClosed for a TCP listener.","triggerScenarios":"Calling Accept() after Close() on the same UDPListener, or after the reader goroutine has shut down and closed acceptCh; typical in accept loops like `for { conn, err := l.Accept(); if err != nil { break } ... }`.","commonSituations":"frps or a vhost UDP worker shutting down while its accept loop is still running; test harness closing the listener then calling Accept once more. Seeing this in logs during process shutdown is expected and benign.","solutions":["Treat it as a shutdown signal: break out of the accept loop and clean up per-connection state, do not retry","Ensure Close() is the last operation: cancel the parent context first so accept loops exit before teardown","If it appears outside of shutdown, look for double-Close or premature Close caused by a failed initialization path"],"exampleFix":"// before\nfor {\n    conn, err := l.Accept()\n    if err != nil { log.Fatalf(\"accept: %v\", err) }\n}\n\n// after\nfor {\n    conn, err := l.Accept()\n    if err != nil {\n        if strings.Contains(err.Error(), \"channel for udp listener closed\") { break }\n        log.Fatalf(\"accept: %v\", err)\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"for {\n    conn, err := l.Accept()\n    if err != nil {\n        if strings.Contains(err.Error(), \"channel for udp listener closed\") {\n            break // normal shutdown\n        }\n        return err\n    }\n    go handle(conn)\n}","preventionTips":["Model Accept error as shutdown sentinel and always break, never log.Fatal","Coordinate Close with accept loops via context so loops exit first","Ignore this line in logs when it coincides with process shutdown"],"tags":["udp","network","shutdown","lifecycle"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}