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

  1. Stop the accept loop when Accept returns this error — treat it as a normal shutdown signal, not a failure
  2. Ensure Close() is called exactly once and only after the accept goroutine has exited (use sync.WaitGroup)
  3. Guard the accept loop with a closed flag or context cancellation before calling Accept again
  4. 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

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


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