ginuerzh/gost · warning

accpet on closed listener

Error message

accpet on closed listener

What it means

kcpListener.Accept returns this error when the listener's errChan is closed, which happens after the listener has been closed. The (typo'd) message indicates Accept was invoked on a listener that is no longer open. It signals the accept loop to stop.

Source

Thrown at kcp.go:401

		}

		cc := &muxStreamConn{Conn: conn, stream: stream}
		select {
		case l.connChan <- cc:
		default:
			cc.Close()
			log.Logf("[kcp] %s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
		}
	}
}

func (l *kcpListener) 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 *kcpListener) Addr() net.Addr {
	return l.ln.Addr()
}

func (l *kcpListener) Close() error {
	return l.ln.Close()
}

func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
	pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)

	switch crypt {
	case "sm4":
		block, _ = kcp.NewSM4BlockCrypt(pass[:16])

View on GitHub (pinned to a33fdbf4c9)

Solutions

  1. Stop the Accept loop when this error is returned (it means the listener is closed) — do not retry.
  2. Synchronize Close with accept goroutines (context cancel, sync.WaitGroup) so Accept is not called after Close.
  3. Treat the error as io.EOF-like termination in server code: return/exit instead of logging-and-continuing.
  4. Guard with a closed flag/atomic so Accept calls after Close are never issued.

Example fix

// before
for {
    conn, err := ln.Accept()
    if err != nil {
        log.Println(err)
        continue // infinite error spam after Close
    }
    go handle(conn)
}
// after
for {
    conn, err := ln.Accept()
    if err != nil {
        if errors.Is(err, errClosedListener) || strings.Contains(err.Error(), "closed listener") {
            return // listener closed
        }
        log.Println(err)
        continue
    }
    go handle(conn)
}
Defensive patterns

Strategy: try-catch

Validate before calling

select {
case <-lnClosed:
	return errors.New("listener already closed")
default:
}
// then call Accept

Type guard

func isClosedListenerErr(err error) bool {
	return err != nil && strings.Contains(err.Error(), "closed listener")
}

Try / catch

conn, err := ln.Accept()
if err != nil {
	if isClosedListenerErr(err) {
		return nil // graceful shutdown
	}
	log.Printf("accept error: %v", err)
	continue
}

Prevention

When it happens

Trigger: Calling Accept on a *kcpListener after Close() was called; concurrent goroutines still running accept loops when the listener is shut down; race between Close and a pending Accept.

Common situations: Server shutdown sequences where Accept loops aren't coordinated with Close; health-check or watchdog goroutines calling Accept during restart; accepting in multiple goroutines while the main thread closes the listener.

Related errors


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