{"record":{"id":"dd8a4b6f95dacc74","repo":"fatedier/frp","slug":"put-conn-error-listener-is-closed","errorCode":null,"errorMessage":"put conn error: listener is closed","messagePattern":"put conn error: listener is closed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/util/net/listener.go","lineNumber":56,"sourceCode":"\nfunc (l *InternalListener) Accept() (net.Conn, error) {\n\tconn, ok := <-l.acceptCh\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"listener closed\")\n\t}\n\treturn conn, nil\n}\n\nfunc (l *InternalListener) PutConn(conn net.Conn) error {\n\terr := errors.PanicToError(func() {\n\t\tselect {\n\t\tcase l.acceptCh <- conn:\n\t\tdefault:\n\t\t\tconn.Close()\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"put conn error: listener is closed\")\n\t}\n\treturn nil\n}\n\nfunc (l *InternalListener) Close() error {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tif !l.closed {\n\t\tclose(l.acceptCh)\n\t\tl.closed = true\n\t}\n\treturn nil\n}\n\nfunc (l *InternalListener) Addr() net.Addr {\n\treturn &InternalAddr{}\n}\n","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/util/net/listener.go#L38-L74","documentation":"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.","triggerScenarios":"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.","commonSituations":"Shutdown races between mux listeners and their internal endpoints; late-arriving connections during graceful drain.","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."],"exampleFix":"// before\nfunc (m *Mux) route(conn net.Conn) {\n    _ = m.httpFallback.PutConn(conn) // conn leaks + error ignored\n}\n\n// after\nif err := m.httpFallback.PutConn(conn); err != nil {\n    conn.Close() // listener closed during shutdown\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := internalListener.PutConn(conn); err != nil {\n    // listener closed during shutdown; conn was not accepted\n    conn.Close()\n    return\n}","preventionTips":["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."],"tags":["listener","concurrency","shutdown","panic-recovery"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}