Skip to content

nvsnap: criu-v2 restore depends on the target PID namespace happening to be free #925

Description

@balajinvda

Problem

criu-v2 restore rebuilds the process tree at the exact PIDs recorded in the
dump, using clone3 with set_tid. It restores into the placeholder pod's
existing PID namespace, so it needs every one of those PIDs to be unused. When
one is not, restore dies at the first collision:

Error (criu/cr-restore.c:1242): Can't fork for 336: File exists

Nothing guarantees they are free. The dump does not serialize a PID namespace,
which the restore log states plainly:

No pidns-1.img image

So correctness here rests on a coincidence: that whatever the placeholder and
the agent have allocated does not overlap the dumped tree's PID range.

Current mitigation

#893 pushes the placeholder's next PID allocation to 100000 before
CRIU runs (reservePlaceholderPIDs, internal/agent/restore_v2.go), which the
agent does from the host because a container cannot write
/proc/sys/kernel/ns_last_pid at all. That removes the realistic collision
source and NIM restores reliably with it.

It is still a mitigation. It narrows the window rather than closing it:

  • It is best-effort. If the bump fails the restore proceeds unprotected, by
    design, so that a failure here cannot break restores that would have worked.
  • It assumes no dumped PID exceeds the floor. True today, but it is an
    assumption about workload shape, not an invariant.
  • Anything that allocates in that namespace between the bump and the clone3 can
    still take a PID.

Proposed fix

Dump and restore the PID namespace, so CRIU creates a fresh one and every PID
in it is available by construction. The exact-PID requirement stops being a
constraint on the environment and becomes an internal detail.

This needs design work rather than a flag:

  • The placeholder currently provides the namespace CRIU restores into. With a
    restored PID namespace, what the placeholder is for, and what pid 1 becomes,
    both change.
  • Kubernetes and containerd have their own view of the container's init
    process. A restored PID namespace has to stay consistent with what the
    runtime believes it is supervising, or lifecycle and reaping break.
  • The cgroup placement work (clone3 CLONE_INTO_CGROUP, see restore_v2.go)
    interacts with this and should be revisited alongside it.

Why it matters beyond the current failure

The risk scales with how many processes a workload starts before it settles,
because that determines how high its session leader PID sits. NIM's entrypoint
chain puts it at 336; vLLM and SGLang land far lower. Larger multi-process
serving stacks will sit higher still, so this becomes more likely as workloads
get bigger, not less.

Acceptance criteria

  • A criu-v2 restore succeeds without depending on the target namespace's PID
    allocation state.
  • The ns_last_pid reservation can be removed, or is demonstrably redundant.
  • A regression test restores a tree whose PIDs are deliberately occupied in the
    target namespace beforehand.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    nvsnapnvsnap GPU checkpoint/restore

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions