Skip to content

Commit f9abc52

Browse files
committed
Remove the hypervisor socket only after confirmed exit
shutdownHypervisor unlinked the control socket via a defer on every return and again before waiting for the process to exit. On the paths where the kill fails and standby resumes the VM, that left a live hypervisor with no socket file, so a later graceful standby or stop could not connect and fell straight into the force-kill path. Unlink the socket only once the hypervisor is provably gone, matching the contract killHypervisor already follows on delete and stop.
1 parent aba44c2 commit f9abc52

2 files changed

Lines changed: 32 additions & 15 deletions

File tree

lib/instances/process_identity_linux_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,25 @@ func TestShutdownHypervisorSparesReusedPIDWhenNoProcessOwnsSocket(t *testing.T)
243243
assert.NoError(t, syscall.Kill(pid, 0), "process with a recycled PID must not be killed")
244244
}
245245

246+
func TestShutdownHypervisorRemovesStaleSocketWhenNoLiveOwner(t *testing.T) {
247+
socketPath := filepath.Join(t.TempDir(), "stale.sock")
248+
require.NoError(t, os.WriteFile(socketPath, nil, 0600))
249+
250+
// No process owns or references the socket and no client factory exists
251+
// for this hypervisor type: the hypervisor is provably gone, so shutdown
252+
// must report success and remove the stale socket file.
253+
m := &manager{}
254+
require.NoError(t, m.shutdownHypervisor(context.Background(), &Instance{
255+
StoredMetadata: StoredMetadata{
256+
Id: "shutdown-stale-socket",
257+
HypervisorType: hypervisor.Type("unregistered-stale-socket-test"),
258+
SocketPath: socketPath,
259+
},
260+
}))
261+
_, statErr := os.Stat(socketPath)
262+
assert.True(t, os.IsNotExist(statErr), "stale socket should be removed once the hypervisor is provably gone")
263+
}
264+
246265
func TestClassifyResolvedHypervisorOwner(t *testing.T) {
247266
const deadPID = 1<<22 - 1
248267
require.False(t, ProcessExists(deadPID))

lib/instances/standby.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -368,11 +368,6 @@ func (m *manager) shutdownHypervisor(ctx context.Context, inst *Instance) error
368368
return fmt.Errorf("confirm hypervisor ownership before shutdown: %w", err)
369369
}
370370

371-
defer func() {
372-
// Clean stale sockets even if graceful shutdown fails.
373-
_ = os.Remove(inst.SocketPath)
374-
}()
375-
376371
// Try to connect to hypervisor
377372
hv, err := m.getHypervisor(inst.SocketPath, inst.HypervisorType)
378373
if err != nil {
@@ -381,10 +376,13 @@ func (m *manager) shutdownHypervisor(ctx context.Context, inst *Instance) error
381376
// alive; teardown is committed, so kill it rather than report a
382377
// completed shutdown for a VMM that is still running.
383378
log.WarnContext(ctx, "could not connect to hypervisor, force killing resolved owner", "instance_id", inst.Id, "pid", pid, "error", err)
384-
return killProcessAndWait(pid)
379+
if err := killProcessAndWait(pid); err != nil {
380+
return err
381+
}
385382
}
386-
// Can't connect - hypervisor might already be stopped
387-
log.DebugContext(ctx, "could not connect to hypervisor, may already be stopped", "instance_id", inst.Id)
383+
// The hypervisor is confirmed gone (killed above or no live owner);
384+
// remove its stale socket.
385+
_ = os.Remove(inst.SocketPath)
388386
return nil
389387
}
390388

@@ -399,11 +397,6 @@ func (m *manager) shutdownHypervisor(ctx context.Context, inst *Instance) error
399397
shutdownErr = hv.Shutdown(ctx)
400398
}
401399

402-
// Teardown is committed; prevent new control-socket clients while the
403-
// hypervisor exits. The deferred remove remains as a fallback for early
404-
// returns above.
405-
_ = os.Remove(inst.SocketPath)
406-
407400
// Wait for process to exit
408401
if pid > 0 {
409402
shouldWaitForGracefulExit := caps.SupportsGracefulVMMShutdown && shutdownErr != hypervisor.ErrNotSupported
@@ -425,8 +418,13 @@ func (m *manager) shutdownHypervisor(ctx context.Context, inst *Instance) error
425418
}
426419

427420
// The hypervisor is confirmed gone (graceful exit, force kill, or no live
428-
// owner). A graceful-API error at this point is not a failure: an error
429-
// from this function means the hypervisor may still be running.
421+
// owner), so its socket is stale now. Removing it any earlier would unlink
422+
// the control socket of a VMM that survives the kill and gets resumed,
423+
// leaving that VM unreachable for a later graceful standby or stop.
424+
_ = os.Remove(inst.SocketPath)
425+
426+
// A graceful-API error at this point is not a failure: an error from this
427+
// function means the hypervisor may still be running.
430428
if shutdownErr != nil && shutdownErr != hypervisor.ErrNotSupported {
431429
log.WarnContext(ctx, "graceful hypervisor shutdown failed, process force killed", "instance_id", inst.Id, "error", shutdownErr)
432430
}

0 commit comments

Comments
 (0)