containerd/containerd · warning

ErrNoSuchProcess

ErrNoSuchProcess

Error message

no such process

What it means

ErrNoSuchProcess in pkg/sys/reaper/reaper_unix.go is returned by Wait when the process being waited on no longer exists — its exit was already reaped or it was never a waitable child of the reaper. The reaper subscribes to child exit events (via go-runc exit channels and unix waiting), and Wait uses this sentinel to signal callers that there is nothing to wait for.

Source

Thrown at pkg/sys/reaper/reaper_unix.go:36

package reaper

import (
	"errors"
	"fmt"
	"maps"
	"os/exec"
	"runtime"
	"sync"
	"syscall"
	"time"

	runc "github.com/containerd/go-runc"
	"golang.org/x/sys/unix"
)

// ErrNoSuchProcess is returned when the process no longer exists
var ErrNoSuchProcess = errors.New("no such process")

const bufferSize = 32

type subscriber struct {
	sync.Mutex
	c      chan runc.Exit
	closed bool
}

func (s *subscriber) close() {
	s.Lock()
	if s.closed {
		s.Unlock()
		return
	}
	close(s.c)
	s.closed = true
	s.Unlock()

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Wait on each process exactly once; coordinate via the reaper's subscription channel rather than calling Wait redundantly.
  2. Treat errors.Is(err, reaper.ErrNoSuchProcess) as 'already exited' and look up the exit status you already received.
  3. Check for goroutines racing to reap the same pid and serialize access.
  4. Verify you are not waiting on a stale/invalid pid after a failed start.

Example fix

// before: two waiters race on the same pid
status, err := reaper.Wait(ctx, pid) // second call -> ErrNoSuchProcess
// after: consume the exit event once via subscription
exitCh, err := reaper.Subscribe()
... // single Wait on the pid; other code reads the runc.Exit from the channel
Defensive patterns

Strategy: try-catch

Validate before calling

// Check the process is still a waitable child before waiting
if !processExists(pid) {
    return ErrAlreadyExited
}

Type guard

func isNoSuchProcess(err error) bool {
    return errors.Is(err, reaper.ErrNoSuchProcess) || errors.Is(err, unix.ESRCH)
}

Try / catch

status, err := reaper.Wait(ctx, pid)
if isNoSuchProcess(err) {
    // already reaped elsewhere; use the exit event already received
    return lookupRecordedExit(pid), nil
}
return status, err

Prevention

When it happens

Trigger: Calling reaper.Wait on a pid whose exit event was already consumed/delivered to a subscriber, or on a pid that has already been reaped; ECHILD-style conditions during unix wait are mapped to this sentinel.

Common situations: Double-waiting on the same container process from two goroutines; waiting after the reaper already published the exit; pid reuse or stale pid after a crash; racing container exit between the daemon and the reaper.

Related errors


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/7efd7105e8d2f7f3. Report an issue: GitHub.