fatedier/frp · info
channel for udp listener closed
Error message
channel for udp listener closed
What it means
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.
Source
Thrown at pkg/util/net/udp.go:241
}()
l.writeCh <- packet
return
}
func (l *UDPListener) WriteMsg(buf []byte, remoteAddr *net.UDPAddr) (err error) {
// only set remote addr here
packet := &UDPPacket{
Buf: buf,
RemoteAddr: remoteAddr,
}
err = l.writeUDPPacket(packet)
return
}
func (l *UDPListener) Accept() (net.Conn, error) {
conn, ok := <-l.acceptCh
if !ok {
return conn, fmt.Errorf("channel for udp listener closed")
}
return conn, nil
}
func (l *UDPListener) Close() error {
if !l.closeFlag {
l.closeFlag = true
if l.readConn != nil {
l.readConn.Close()
}
}
return nil
}
func (l *UDPListener) Addr() net.Addr {
return l.addr
}
View on GitHub (pinned to 6c8a8d0a97)
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
Example fix
// before
for {
conn, err := l.Accept()
if err != nil { log.Fatalf("accept: %v", err) }
}
// after
for {
conn, err := l.Accept()
if err != nil {
if strings.Contains(err.Error(), "channel for udp listener closed") { break }
log.Fatalf("accept: %v", err)
}
} Defensive patterns
Strategy: try-catch
Try / catch
for {
conn, err := l.Accept()
if err != nil {
if strings.Contains(err.Error(), "channel for udp listener closed") {
break // normal shutdown
}
return err
}
go handle(conn)
} Prevention
- 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
When it happens
Trigger: 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 } ... }`.
Common situations: 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.
Related errors
- udp write closed listener
- listener closed
- ssh tunnel server closed
- control manager is closed
- acquire port %d error: %v
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/1285e6b14a2f6098.
Report an issue: GitHub.