ginuerzh/gost · warning
accpet on closed listener
Error message
accpet on closed listener
What it means
udpRemoteForwardListener.Accept returns this error when the listener has already been closed. The Accept select blocks on connChan for incoming forwarded UDP connections, but if the listener's closed channel fires first, it returns a plain error stating Accept was called on a closed listener. This mirrors net.Listener semantics where Accept after Close must return an error.
Source
Thrown at forward.go:771
} else {
tempDelay *= 2
}
if max := 6 * time.Second; tempDelay > max {
tempDelay = max
}
log.Logf("[rudp] Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return
}
}
func (l *udpRemoteForwardListener) Accept() (conn net.Conn, err error) {
select {
case conn = <-l.connChan:
case <-l.closed:
err = errors.New("accpet on closed listener")
}
return
}
func (l *udpRemoteForwardListener) Addr() net.Addr {
return l.addr
}
func (l *udpRemoteForwardListener) Close() error {
l.closeMux.Lock()
defer l.closeMux.Unlock()
select {
case <-l.closed:
return nil
default:
l.connMap.Range(func(k interface{}, v *udpServerConn) bool {
v.Close()View on GitHub (pinned to a33fdbf4c9)
Solutions
- Stop the accept loop when Accept returns this error — treat it as a normal shutdown signal, not a failure
- Ensure Close() is called exactly once and only after the accept goroutine has exited (use sync.WaitGroup)
- Guard the accept loop with a closed flag or context cancellation before calling Accept again
- Fix the typo'd message aside, wrap the error with context so shutdown logs are distinguishable from real Accept failures
Example fix
// before
for {
c, err := l.Accept()
if err != nil { log.Fatal(err) }
go handle(c)
}
// after
for {
c, err := l.Accept()
if err != nil {
if strings.Contains(err.Error(), "closed listener") { return } // normal shutdown
log.Printf("accept: %v", err); return
}
go handle(c)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go has no pre-check; track listener lifecycle yourself
if l.isClosed() { return errors.New("listener already closed, skipping Accept") } Type guard
func isListenerClosed(err error) bool { return err != nil && strings.Contains(err.Error(), "closed listener") } Try / catch
conn, err := l.Accept()
if err != nil {
if isListenerClosed(err) { return nil } // normal shutdown
return fmt.Errorf("udp forward accept: %w", err)
} Prevention
- Treat any error from Accept as terminal for the accept loop
- Close listeners exactly once (sync.Once) and after accept goroutines exit
- Use WaitGroups to join accept loops before process shutdown
When it happens
Trigger: Calling Accept() on a udpRemoteForwardListener after Close() has been invoked (or concurrently while another goroutine closes it), typically in an accept loop that keeps looping after shutdown.
Common situations: Port-forward shutdown races: the main goroutine closes the listener on config reload or exit while the accept loop is still blocked in Accept; goroutine-leak cleanup code closing listeners twice; accepting in a loop without checking listener health.
Related errors
- accpet on closed listener
- accpet on closed listener
- kcp: wrong connection type
- ciphertext too short
- UDP redirect is not available on the Windows platform
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/534a332d417ec07f.
Report an issue: GitHub.