fatedier/frp · error
udp write closed listener
Error message
udp write closed listener
What it means
Thrown by UDPListener.WriteMsg (via writeUDPPacket) in frp's UDP listener wrapper. Sending a packet is implemented by pushing to an unbuffered workflow channel l.writeCh; if the listener has already been Closed (which closes writeCh), the channel send panics, and the deferred recover converts that panic into this error. It therefore means: you wrote to a UDP listener after it was shut down.
Source
Thrown at pkg/util/net/udp.go:221
for {
packet, ok := <-l.writeCh
if !ok {
return
}
if addr, ok := packet.RemoteAddr.(*net.UDPAddr); ok {
_, _ = readConn.WriteToUDP(packet.Buf, addr)
}
}
}()
return
}
func (l *UDPListener) writeUDPPacket(packet *UDPPacket) (err error) {
defer func() {
if errRet := recover(); errRet != nil {
err = fmt.Errorf("udp write closed listener")
}
}()
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.acceptChView on GitHub (pinned to 6c8a8d0a97)
Solutions
- Stop all goroutines that call WriteMsg (context cancellation, WaitGroup) before calling UDPListener.Close()
- Guard writes with the listener's closed state (check l.closeFlag under a lock) and drop the packet if closed
- If you own the send loop, select on <-l.closeCh / ctx.Done() instead of a bare channel send
- In frp itself, this usually surfaces during shutdown: upgrade/restart frps and frpc together so both ends stop cleanly
Example fix
// before
l.writeUDPPacket(packet) // may panic->error if listener closed
// after
if l.closeFlag {
return fmt.Errorf("udp write closed listener")
}
err = l.writeUDPPacket(packet) Defensive patterns
Strategy: validation
Validate before calling
// before writing, confirm the listener is still open
if l.closeFlag { // package-internal; if external, track Close() in your own flag
return
}
err := conn.WriteMsg(buf, addr) Try / catch
if err := udpListener.WriteMsg(buf, addr); err != nil && strings.Contains(err.Error(), "udp write closed listener") {
// listener already closed: stop the writer goroutine, do not retry
return
} Prevention
- Close the listener last: cancel writer goroutines (context/WaitGroup) before calling Close()
- Never share a UDPListener across goroutines without a documented ownership rule for Close
- In tests, use t.Cleanup to close listeners after, not before, dependent goroutines stop
When it happens
Trigger: Calling UDPListener.WriteMsg (or WriteToUDP-style send) after UDPListener.Close() has run, or concurrently with Close(). The goroutine that drains writeCh has exited because readConn was closed, so the send on the closed channel panics and is recovered here.
Common situations: Client disconnect and server shutdown racing in frps's UDP vhost/proxy handling; a metrics or keepalive goroutine still writing stats/Echo packets after the listener teardown began; test code forgetting to stop a writer goroutine before closing the listener.
Related errors
- channel for udp listener closed
- channel for kcp listener closed
- listener closed
- put conn error: listener is closed
- listener closed
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/eb6eb88b73410aea.
Report an issue: GitHub.