ginuerzh/gost · info
listener has been closed
Error message
listener has been closed
What it means
tunListener.Close uses a `closed` channel as a once-guard: if the channel is already closed, it returns "listener has been closed" instead of nil. This deviates from the usual net.Listener idiom where Close is idempotent, so a double Close surfaces as an error.
Source
Thrown at tuntap.go:115
func (l *tunListener) Accept() (net.Conn, error) {
select {
case conn := <-l.conns:
return conn, nil
case <-l.closed:
}
return nil, errors.New("accept on closed listener")
}
func (l *tunListener) Addr() net.Addr {
return l.addr
}
func (l *tunListener) Close() error {
select {
case <-l.closed:
return errors.New("listener has been closed")
default:
close(l.closed)
}
return nil
}
type tunHandler struct {
options *HandlerOptions
routes sync.Map
chExit chan struct{}
}
// TunHandler creates a handler for tun tunnel.
func TunHandler(opts ...HandlerOption) Handler {
h := &tunHandler{
options: &HandlerOptions{},
chExit: make(chan struct{}, 1),
}View on GitHub (pinned to a33fdbf4c9)
Solutions
- Call Close only once per listener lifecycle; rely on the closed channel to signal other goroutines
- Ignore this specific error in cleanup paths (treat as success, like net.ErrClosed handling)
- Wrap Close in a sync.Once in your own code to guarantee idempotency
Example fix
// before
defer ln.Close()
...
if err := ln.Close(); err != nil { log.Fatal(err) }
// after
var closeOnce sync.Once
closeFn := func() { closeOnce.Do(func() { ln.Close() }) }
defer closeFn()
... Defensive patterns
Strategy: validation
Validate before calling
var closed atomic.Bool
// before calling Close:
if closed.CompareAndSwap(false, true) {
if err := ln.Close(); err != nil { log.Printf("close: %v", err) }
} Try / catch
if err := ln.Close(); err != nil {
if strings.Contains(err.Error(), "has been closed") {
return nil // idempotent close: treat as success
}
return err
} Prevention
- Wrap Close in sync.Once to make it idempotent at the call site
- Avoid defer ln.Close() plus an explicit Close on the same listener
- Ignore "has been closed" errors in cleanup/teardown paths
When it happens
Trigger: Calling Close() twice on a tunListener, e.g. both an explicit shutdown path and a defer ln.Close(); Close after an external component already closed the listener.
Common situations: defer + explicit close patterns; restart/reload logic that closes listeners in more than one place; tests calling t.Cleanup close plus manual close.
Related errors
- accept on closed listener
- closed
- accpet on closed listener
- accpet on closed listener
- accpet on closed listener
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/cccf5790f34ae792.
Report an issue: GitHub.