ginuerzh/gost · warning
accpet on closed listener
Error message
accpet on closed listener
What it means
fakeTCPListener.Accept returns this error when the listener's errChan is closed, which happens when the listener has been closed. Accept selects between receiving a connection from connChan and an error from errChan; a closed errChan (ok == false) means the listener is shut down and no further connections will arrive. It is the fakeTCP (relay-over-UDP) equivalent of the standard 'use of closed network connection' error.
Source
Thrown at ftcp.go:139
select {
case conn.rChan <- b[:n]:
if Debug {
log.Logf("[ftcp] %s >>> %s : length %d", raddr, l.Addr(), n)
}
default:
log.Logf("[ftcp] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan))
}
}
}
func (l *fakeTCPListener) Accept() (conn net.Conn, err error) {
var ok bool
select {
case conn = <-l.connChan:
case err, ok = <-l.errChan:
if !ok {
err = errors.New("accpet on closed listener")
}
}
return
}
func (l *fakeTCPListener) Addr() net.Addr {
return l.ln.LocalAddr()
}
func (l *fakeTCPListener) Close() error {
err := l.ln.Close()
l.connMap.Range(func(k interface{}, v *udpServerConn) bool {
v.Close()
return true
})
return err
}View on GitHub (pinned to a33fdbf4c9)
Solutions
- Exit the accept loop when this error is returned — after listener close it will be returned forever
- Coordinate shutdown: signal the accept goroutine to stop before/when calling Close()
- Avoid double-closing the listener (use sync.Once around Close)
- Log it at debug level; it is expected during orderly shutdown
Example fix
// before
c, err := ln.Accept()
if err != nil { return fmt.Errorf("accept: %w", err) }
// after
c, err := ln.Accept()
if err != nil {
if strings.Contains(err.Error(), "closed listener") { return nil } // listener closed, stop loop
return fmt.Errorf("accept: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Track shutdown state explicitly before looping
select {
case <-ln.closedChan:
return // do not call Accept
default:
} Type guard
func isListenerClosedErr(err error) bool { return err != nil && strings.Contains(err.Error(), "closed listener") } Try / catch
conn, err := ln.Accept()
if err != nil {
if isListenerClosedErr(err) { return nil } // expected at shutdown
return fmt.Errorf("fakeTCP accept: %w", err)
} Prevention
- Break out of the accept loop on the first Accept error
- Guard Close with sync.Once to avoid double-close racing Accept
- Signal accept goroutines to stop before calling Close
When it happens
Trigger: Calling Accept() on a fakeTCPListener after Close() closed errChan, or racing with Close() during shutdown while an accept loop is still running.
Common situations: Relay service shutdown while worker goroutines still sit in Accept; double-close of the listener from both a timeout handler and an explicit Stop; accept loops that treat every Accept error as retryable and spin on the closed channel.
Related errors
- accpet on closed listener
- read not supported
- accpet on closed listener
- not a TCP connection
- write: data maximum exceeded
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/e88a9668bf2faa0f.
Report an issue: GitHub.