ginuerzh/gost · info
closed
Error message
closed
What it means
tcpRemoteForwardListener.Accept returns 'closed' when the listener's closed channel has been fired, i.e. Accept was called after Close(). This is the standard net.Listener convention signaling the listener is shutting down and no more connections will be delivered on connChan.
Source
Thrown at forward.go:446
select {
case l.connChan <- conn:
default:
conn.Close()
log.Logf("[rtcp] %s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
}
}
}
func (l *tcpRemoteForwardListener) Accept() (conn net.Conn, err error) {
if l.ln != nil {
return l.ln.Accept()
}
select {
case conn = <-l.connChan:
case <-l.closed:
err = errors.New("closed")
}
return
}
func (l *tcpRemoteForwardListener) accept() (conn net.Conn, err error) {
lastNode := l.chain.LastNode()
if lastNode.Protocol == "forward" && lastNode.Transport == "ssh" {
return l.chain.Dial(l.addr.String())
}
if l.isChainValid() {
if lastNode.GetBool("mbind") {
return l.muxAccept() // multiplexing support for binding.
}
cc, er := l.chain.Conn()
if er != nil {View on GitHub (pinned to a33fdbf4c9)
Solutions
- Stop the Accept loop when this error is returned (standard shutdown path)
- Synchronize shutdown: close listener only after Accept loop exits or accept the benign error
- Compare error message/type to 'closed' and return io.EOF/ErrListenerClosed semantics upward
- Avoid logging it as fatal during shutdown
Example fix
// before
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
go handle(conn)
}
// after
for {
conn, err := l.Accept()
if err != nil {
if err.Error() == "closed" {
return // listener shut down; exit loop cleanly
}
log.Log(err)
continue
}
go handle(conn)
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check listener state before entering the accept loop
select {
case <-l.closed:
return // already closed; skip accept loop entirely
default:
} Type guard
func isListenerClosed(err error) bool {
return err != nil && err.Error() == "closed"
} Try / catch
conn, err := l.Accept()
if err != nil {
if isListenerClosed(err) {
return nil // graceful shutdown: stop accepting
}
// handle transient error with tempDelay backoff
return err
} Prevention
- Exit accept loops immediately on this error rather than retrying
- Serialize Close() and Accept() shutdown ordering in one shutdown path
- Normalize the error to io.ErrClosedPipe/ErrListenerClosed in wrappers
- Never log 'closed' as fatal during teardown
When it happens
Trigger: Calling Accept on a remote-forward TCP listener after Close() was invoked, or racing Close with Accept so the select falls into the <-l.closed branch.
Common situations: Graceful shutdown of a remote port-forwarding server (gost rtcp), Stop/reload of services, test teardown ordering issues.
Related errors
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/e6a81565cbed4c97.
Report an issue: GitHub.