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

  1. Treat this error as 'endpoint is shutting down': close the incoming conn yourself and stop routing to this listener.
  2. Order teardown so producers (mux) stop accepting before closing the InternalListener.
  3. 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

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


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/dd8a4b6f95dacc74. Report an issue: GitHub.