ginuerzh/gost · info

connection is closed

Error message

connection is closed

What it means

udpServerConn.Close returns this error when the connection has already been closed. A `closed` channel guarded by a mutex makes Close non-idempotent: a second call finds the channel closed and returns this sentinel instead of re-running cleanup.

Source

Thrown at udp.go:290

			log.Logf("[udp] %s <<< %s : length %d", addr, c.LocalAddr(), n)
		}

		select {
		case c.nopChan <- n:
		default:
		}
	}

	return
}

func (c *udpServerConn) Close() error {
	c.closeMutex.Lock()
	defer c.closeMutex.Unlock()

	select {
	case <-c.closed:
		return errors.New("connection is closed")
	default:
		if c.config.onClose != nil {
			c.config.onClose()
		}
		close(c.closed)
	}
	return nil
}

func (c *udpServerConn) ttlWait() {
	ttl := c.config.ttl
	if ttl == 0 {
		ttl = defaultTTL
	}
	timer := time.NewTimer(ttl)
	defer timer.Stop()

	for {

View on GitHub (pinned to a33fdbf4c9)

Solutions

  1. Close the connection exactly once; consolidate cleanup into a single path (e.g. sync.Once).
  2. Ignore this error in deferred Close calls — double close performs no action and is harmless.
  3. If Close races with ttlWait, prefer relying on the library's TTL close and skip the manual close, or remove the ttl configured.

Example fix

// before
defer conn.Close() // plus explicit conn.Close() on error path -> 'connection is closed'
// after
var closeOnce sync.Once
closeConn := func() { closeOnce.Do(func() { conn.Close() }) }
defer closeConn()
...
closeConn()
Defensive patterns

Strategy: try-catch

Validate before calling

var closedOnce sync.Once
func closeConn(c *udpServerConn) {
    closedOnce.Do(func() { c.Close() })
}

Type guard

func isConnClosedErr(err error) bool {
    return err != nil && err.Error() == "connection is closed"
}

Try / catch

if err := conn.Close(); err != nil && !isConnClosedErr(err) {
    log.Printf("close conn: %v", err)
}

Prevention

When it happens

Trigger: Calling Close twice on a *udpServerConn — e.g. via defer plus an explicit close, or concurrent Close calls from ttlWait and application shutdown code.

Common situations: Idle-TTL cleanup (ttlWait) closing the connection while the user code also closes it; error-handling paths that close the conn and then a deferred Close fires again.

Related errors


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