fatedier/frp · warning
put conn error: listener is closed
Error message
put conn error: listener is closed
What it means
InternalListener.PutConn sends on acceptCh inside errors.PanicToError. If the channel is already closed (listener closed), the send panics; PanicToError converts the panic to an error and PutConn wraps it with this message, also meaning the offered conn was NOT accepted (and is not closed by this path — callers should close it). A full channel does not panic: the default branch closes the conn instead.
Source
Thrown at pkg/util/net/listener.go:56
func (l *InternalListener) Accept() (net.Conn, error) {
conn, ok := <-l.acceptCh
if !ok {
return nil, fmt.Errorf("listener closed")
}
return conn, nil
}
func (l *InternalListener) PutConn(conn net.Conn) error {
err := errors.PanicToError(func() {
select {
case l.acceptCh <- conn:
default:
conn.Close()
}
})
if err != nil {
return fmt.Errorf("put conn error: listener is closed")
}
return nil
}
func (l *InternalListener) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
if !l.closed {
close(l.acceptCh)
l.closed = true
}
return nil
}
func (l *InternalListener) Addr() net.Addr {
return &InternalAddr{}
}
View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Treat this error as 'endpoint is shutting down': close the incoming conn yourself and stop routing to this listener.
- Order teardown so producers (mux) stop accepting before closing the InternalListener.
- Check the closed flag / stop serving before PutConn in hot paths if the race is frequent.
Example fix
// before
func (m *Mux) route(conn net.Conn) {
_ = m.httpFallback.PutConn(conn) // conn leaks + error ignored
}
// after
if err := m.httpFallback.PutConn(conn); err != nil {
conn.Close() // listener closed during shutdown
} Defensive patterns
Strategy: try-catch
Try / catch
if err := internalListener.PutConn(conn); err != nil {
// listener closed during shutdown; conn was not accepted
conn.Close()
return
} Prevention
- Always close the conn yourself when PutConn errors.
- Stop producers (muxes) before closing the InternalListener during teardown.
- Note a full buffer is different: PutConn returns nil but closes the conn — apply backpressure upstream.
When it happens
Trigger: A connection producer calling PutConn concurrently with or after InternalListener.Close() — e.g. an HTTPS mux still routing a new connection to a fallback vhost that has just been closed during frps shutdown.
Common situations: Shutdown races between mux listeners and their internal endpoints; late-arriving connections during graceful drain.
Related errors
- channel for kcp listener closed
- listener closed
- udp write closed listener
- control manager is closed
- ssh tunnel server closed
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/dd8a4b6f95dacc74.
Report an issue: GitHub.