ginuerzh/gost · warning

accept on closed listener

Error message

accept on closed listener

What it means

tunListener.Accept selects between an incoming connection and the closed channel; once Close() has closed the `closed` channel, Accept returns "accept on closed listener". It is the tun-device listener's way of signaling permanent shutdown.

Source

Thrown at tuntap.go:105

		addrs, _ := ifce.Addrs()
		log.Logf("[tun] %s: name: %s, mtu: %d, addrs: %s",
			conn.LocalAddr(), ifce.Name, ifce.MTU, addrs)

		ln.conns <- conn
	}

	return ln, nil
}

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

View on GitHub (pinned to a33fdbf4c9)

Solutions

  1. Break out of the accept loop when this error is returned rather than retrying
  2. Stop the accept goroutine before or via the same signal that triggers Close
  3. Match on the error string (or wrap a sentinel) to distinguish shutdown from transient accept errors

Example fix

// before
conn, err := ln.Accept()
if err != nil { return err }
// after
conn, err := ln.Accept()
if err != nil {
    if err.Error() == "accept on closed listener" { return nil }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// guard the accept loop with a shutdown flag set before ln.Close()
var closing atomic.Bool

Try / catch

conn, err := ln.Accept()
if err != nil {
    if strings.Contains(err.Error(), "closed listener") {
        return nil // clean shutdown
    }
    return err
}

Prevention

When it happens

Trigger: Calling Accept on a tunListener after Close(); a Close/Accept race where the closed channel is closed while Accept is blocked in the select.

Common situations: Graceful shutdown of TUN-based tunnels; deferred Close firing while an accept loop is still running; test teardown closing listeners before drain goroutines exit.

Related errors


AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02). Data as JSON: /api/errors/d873d2390ce9cb14. Report an issue: GitHub.