hashicorp/nomad · error

%q has already been closed

Error message

%q has already been closed

What it means

errorIfClosed guards every operation on a netNS handle; this error is returned when Set(), Do(), or any use after Close() is attempted on an already-closed NetNS. Once closed, the underlying file is closed and the handle cannot be reused.

Source

Thrown at client/lib/nsutil/ns_linux.go:169

	fd, err := os.Open(nspath)
	if err != nil {
		return nil, err
	}

	return &netNS{file: fd}, nil
}

func (ns *netNS) Path() string {
	return ns.file.Name()
}

func (ns *netNS) Fd() uintptr {
	return ns.file.Fd()
}

func (ns *netNS) errorIfClosed() error {
	if ns.closed {
		return fmt.Errorf("%q has already been closed", ns.file.Name())
	}
	return nil
}

func (ns *netNS) Do(toRun func(NetNS) error) error {
	if err := ns.errorIfClosed(); err != nil {
		return err
	}

	containedCall := func(hostNS NetNS) error {
		threadNS, err := GetCurrentNS()
		if err != nil {
			return fmt.Errorf("failed to open current netns: %v", err)
		}
		defer threadNS.Close()

		// switch to target namespace
		if err = ns.Set(); err != nil {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Do not call Close() until all Set()/Do() usage of the handle is finished
  2. Re-open the namespace with GetNS(nspath) or GetCurrentNS() instead of reusing the closed handle
  3. Audit defer ordering so Close() is the last action referencing the handle
  4. Guard concurrent use with ownership/lifetime management rather than sharing the handle across goroutines after close

Example fix

// before
hostNS, _ := GetCurrentNS()
defer hostNS.Close()
// ... later in another goroutine
hostNS.Set() // "has already been closed"
// after
hostNS, _ := GetCurrentNS()
err := doWork(hostNS) // all use happens here
defer hostNS.Close()  // close only after last use
Defensive patterns

Strategy: validation

Validate before calling

func ensureOpen(ns *netNS) error {
    if ns == nil || ns.closed {
        return errors.New("netns closed: reopen with GetNS/GetCurrentNS before use")
    }
    return nil
}

Type guard

func isOpen(ns NetNS) bool {
    n, ok := ns.(*netNS)
    return ok && !n.closed
}

Try / catch

if err := ns.Set(); err != nil {
    if strings.Contains(err.Error(), "has already been closed") {
        ns, err = GetNS(nsPath) // reopen instead of failing
        if err != nil { return err }
        return ns.Set()
    }
    return err
}

Prevention

When it happens

Trigger: Calling Set() or Do() on a NetNS after calling Close() on it; calling Set() on the saved hostNS/threadNS handle after a deferred Close() already ran; reusing a NetNS obtained from GetNS/GetCurrentNS after the defer cleanup fired.

Common situations: Deferred Close() firing before a later goroutine/closure uses the handle; calling Do() twice where the first defer closed the handle; storing a NetNS long-term and using it after a shutdown path closed it.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/ec0ffd5a729dc7a2. Report an issue: GitHub.