Skip to content

Commit 25ecff7

Browse files
committed
Use Start's context correctly
VMCommandBuilder#Build()'s context directly controls exec.Cmd. To test the goroutines inside Machine#startVMM(), we cannot use VMCommandBuilder. Signed-off-by: Kazuyoshi Kato <[email protected]>
1 parent ec7ef18 commit 25ecff7

File tree

2 files changed

+43
-34
lines changed

2 files changed

+43
-34
lines changed

machine.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,10 @@ func NewMachine(ctx context.Context, cfg Config, opts ...Opt) (*Machine, error)
338338
return m, nil
339339
}
340340

341-
// Start will iterate through the handler list and call each handler. If an
341+
// Start actually start a Firecracker microVM.
342+
// The context must not be cancelled while the microVM is running.
343+
//
344+
// It will iterate through the handler list and call each handler. If an
342345
// error occurred during handler execution, that error will be returned. If the
343346
// handlers succeed, then this will start the VMM instance.
344347
// Start may only be called once per Machine. Subsequent calls will return
@@ -521,7 +524,10 @@ func (m *Machine) startVMM(ctx context.Context) error {
521524
// but doesn't tell anyone about that.
522525
go func() {
523526
<-ctx.Done()
524-
m.stopVMM()
527+
err := m.stopVMM()
528+
if err != nil {
529+
m.logger.WithError(err).Errorf("failed to stop vm %q", m.Cfg.VMID)
530+
}
525531
}()
526532

527533
// This goroutine is used to tell clients that the process is stopped

machine_test.go

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,18 +1089,18 @@ func TestWait(t *testing.T) {
10891089

10901090
cases := []struct {
10911091
name string
1092-
stop func(m *Machine)
1092+
stop func(m *Machine, cancel context.CancelFunc)
10931093
}{
10941094
{
10951095
name: "StopVMM",
1096-
stop: func(m *Machine) {
1096+
stop: func(m *Machine, _ context.CancelFunc) {
10971097
err := m.StopVMM()
10981098
require.NoError(t, err)
10991099
},
11001100
},
11011101
{
11021102
name: "Kill",
1103-
stop: func(m *Machine) {
1103+
stop: func(m *Machine, cancel context.CancelFunc) {
11041104
pid, err := m.PID()
11051105
require.NoError(t, err)
11061106

@@ -1111,6 +1111,17 @@ func TestWait(t *testing.T) {
11111111
},
11121112
{
11131113
name: "Context Cancel",
1114+
stop: func(m *Machine, cancel context.CancelFunc) {
1115+
cancel()
1116+
},
1117+
},
1118+
{
1119+
name: "StopVMM + Context Cancel",
1120+
stop: func(m *Machine, cancel context.CancelFunc) {
1121+
m.StopVMM()
1122+
time.Sleep(1 * time.Second)
1123+
cancel()
1124+
},
11141125
},
11151126
}
11161127

@@ -1123,50 +1134,42 @@ func TestWait(t *testing.T) {
11231134
defer os.Remove(socketPath)
11241135

11251136
cfg := createValidConfig(t, socketPath)
1126-
cmd := VMCommandBuilder{}.
1127-
WithSocketPath(cfg.SocketPath).
1128-
WithBin(getFirecrackerBinaryPath()).
1129-
Build(vmContext)
1130-
m, err := NewMachine(ctx, cfg, WithProcessRunner(cmd))
1137+
m, err := NewMachine(ctx, cfg, func(m *Machine) {
1138+
// Rewriting m.cmd partially wouldn't work since Cmd has
1139+
// some unexported members
1140+
args := m.cmd.Args[1:]
1141+
m.cmd = exec.Command(getFirecrackerBinaryPath(), args...)
1142+
})
11311143
require.NoError(t, err)
11321144

1133-
err = m.Start(ctx)
1145+
err = m.Start(vmContext)
11341146
require.NoError(t, err)
11351147

11361148
pid, err := m.PID()
11371149
require.NoError(t, err)
11381150

1151+
var wg sync.WaitGroup
1152+
wg.Add(1)
11391153
go func() {
1140-
if c.stop != nil {
1141-
c.stop(m)
1142-
} else {
1143-
vmCancel()
1144-
}
1154+
defer wg.Done()
1155+
c.stop(m, vmCancel)
11451156
}()
11461157

11471158
err = m.Wait(ctx)
11481159
require.Error(t, err, "Firecracker was killed and it must be reported")
1160+
t.Logf("err = %v", err)
11491161

1150-
alive, err := isProcessAlive(pid)
1151-
require.False(t, alive, "pid=%d is still there", pid)
1152-
})
1153-
}
1154-
}
1162+
proc, err := os.FindProcess(pid)
1163+
// Having an error here doesn't mean the process is not there.
1164+
// In fact it won't be non-nil on Unix systems
1165+
require.NoError(t, err)
11551166

1156-
func isProcessAlive(pid int) (bool, error) {
1157-
// Using kill(2) with signal=0 to check the existence of the process,
1158-
// because os.FindProcess always returns a process, regardless of whether the process is
1159-
// alive or not.
1160-
// https://golang.org/pkg/os/#FindProcess
1161-
err := syscall.Kill(pid, syscall.Signal(0))
1162-
if err != nil {
1163-
if errno, ok := err.(syscall.Errno); ok {
1164-
if errno == syscall.ESRCH {
1165-
return false, nil
1166-
}
1167-
}
1167+
err = proc.Signal(syscall.Signal(0))
1168+
require.Equal(t, "os: process already finished", err.Error())
1169+
1170+
wg.Wait()
1171+
})
11681172
}
1169-
return true, nil
11701173
}
11711174

11721175
func TestWaitWithInvalidBinary(t *testing.T) {

0 commit comments

Comments
 (0)