Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/instances/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ func (m *manager) startAndBootVM(
}

func resolveRuntimeHypervisorPID(log *slog.Logger, socketPath string, fallbackPID int) int {
if processExists(fallbackPID) {
if ProcessExists(fallbackPID) {
return fallbackPID
}
pid, err := hypervisor.ResolveProcessPID(socketPath)
Expand Down
11 changes: 9 additions & 2 deletions lib/instances/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,18 @@ func (m *manager) killHypervisor(ctx context.Context, inst *Instance) error {
for i := 0; i < 50; i++ { // 50 * 100ms = 5 seconds
var wstatus syscall.WaitStatus
wpid, err := syscall.Wait4(pid, &wstatus, syscall.WNOHANG, nil)
if err != nil || wpid == pid {
// Process reaped successfully or error (likely ECHILD if already reaped)
if err == nil && wpid == pid {
log.DebugContext(ctx, "hypervisor process killed and reaped", "instance_id", inst.Id, "pid", pid)
break
}
if err != nil {
// Wait4 returns ECHILD when the hypervisor is not our child
// (e.g. after a hypeman restart); wait until it has exited.
if killErr := syscall.Kill(pid, 0); killErr == syscall.ESRCH {
log.DebugContext(ctx, "hypervisor process killed", "instance_id", inst.Id, "pid", pid)
break
}
}
if i == 49 {
log.WarnContext(ctx, "hypervisor process did not exit in time", "instance_id", inst.Id, "pid", pid)
}
Expand Down
2 changes: 1 addition & 1 deletion lib/instances/guestmemory_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func requireHypervisorPID(t *testing.T, ctx context.Context, mgr *manager, insta
t.Helper()
inst, err := mgr.GetInstance(ctx, instanceID)
require.NoError(t, err)
if inst.HypervisorPID != nil && processExists(*inst.HypervisorPID) {
if inst.HypervisorPID != nil && ProcessExists(*inst.HypervisorPID) {
return *inst.HypervisorPID
}
if pid, err := hypervisor.ResolveProcessPID(inst.SocketPath); err == nil {
Expand Down
17 changes: 17 additions & 0 deletions lib/instances/process_identity_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build linux

package instances

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHypervisorProcessExistsRejectsLivePIDWithoutSocketOwnership(t *testing.T) {
t.Parallel()

assert.False(t, HypervisorProcessExists(os.Getpid(), filepath.Join(t.TempDir(), "missing.sock")))
}
20 changes: 18 additions & 2 deletions lib/instances/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ func refreshHypervisorPID(stored *StoredMetadata, state State) {
if !state.RequiresVMM() && state != StateUnknown {
return
}
if stored.HypervisorPID != nil && processExists(*stored.HypervisorPID) {
if stored.HypervisorPID != nil && ProcessExists(*stored.HypervisorPID) {
return
}
if stored.SocketPath == "" {
Expand All @@ -587,7 +587,23 @@ func refreshHypervisorPID(stored *StoredMetadata, state State) {
}
}

func processExists(pid int) bool {
// HypervisorProcessExists reports whether pid owns the instance's hypervisor socket.
func HypervisorProcessExists(pid int, socketPath string) bool {
if !ProcessExists(pid) {
return false
}
if runtime.GOOS != "linux" {
return true
}
if socketPath == "" {
return false
}
resolvedPID, err := hypervisor.ResolveProcessPID(socketPath)
return err == nil && resolvedPID == pid
}

// ProcessExists reports whether pid belongs to a live, non-zombie process.
func ProcessExists(pid int) bool {
if pid <= 0 {
return false
}
Expand Down