99 "net"
1010 "os"
1111 "os/exec"
12+ "os/signal"
1213 "syscall"
1314 "time"
1415
@@ -72,17 +73,14 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
7273 return nil , nil , err
7374 }
7475 // Set user networking with gvproxy
75-
7676 gvproxySocket , err := mc .GVProxySocket ()
7777 if err != nil {
7878 return nil , nil , err
7979 }
80-
8180 // Wait on gvproxy to be running and aware
8281 if err := sockets .WaitForSocketWithBackoffs (gvProxyMaxBackoffAttempts , gvProxyWaitBackoff , gvproxySocket .GetPath (), "gvproxy" ); err != nil {
8382 return nil , nil , err
8483 }
85-
8684 netDevice .SetUnixSocketPath (gvproxySocket .GetPath ())
8785
8886 // create a one-time virtual machine for starting because we dont want all this information in the
@@ -244,24 +242,62 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
244242 return nil , nil , err
245243 }
246244
247- returnFunc := func () error {
245+ // Start a goroutine that will evenutally propagate a
246+ // terminate signal to the VM process.
247+ // If the user decides to abort `podman machine start`
248+ // while the VM is starting, we want the VM to be stopped
249+ // too. Or the machine will be left in an inconsistent
250+ // state. Note that the goroutine will wait until the
251+ // the main goroutine completes.
252+ term := make (chan os.Signal , 1 )
253+ signal .Notify (term , os .Interrupt , syscall .SIGTERM )
254+ // Get the PID first because cmd.Process will
255+ // be released in the main thread
256+ pid := cmd .Process .Pid
257+ go func () {
258+ <- term
259+ logrus .Debugf ("Termination signal forwarded to the VM process (PID: %d)\n " , pid )
260+ p , err := os .FindProcess (pid )
261+ if err != nil {
262+ logrus .Errorf ("Failed to find process %d: %v" , pid , err )
263+ return
264+ }
265+ err = p .Signal (os .Interrupt )
266+ if err != nil {
267+ logrus .Errorf ("Termination signal received, but terminating the VM process (PID: %d) failed: %v" , pid , err )
268+ return
269+ }
270+ // Wait and release the resources associated with the process
271+ if _ , err := p .Wait (); err != nil {
272+ logrus .Debugf ("Failed waiting for the process after terminating it: %v" , err )
273+ }
274+ }()
275+
276+ // waitForReadyFunc is the callback that the caller of StartVM()
277+ // uses to block its execution until the the VM is ready or an error
278+ // occurs
279+ waitForReadyFunc := func () error {
248280 processErrChan := make (chan error )
249281 ctx , cancel := context .WithCancel (context .Background ())
250282 defer cancel ()
283+ // The VM readiness will be communicated on readyChan. In the
284+ // meantime, the following goroutine checks every 500ms that the VM
285+ // process is running. If it's not, returns an error on processErrChan.
251286 go func () {
252287 defer close (processErrChan )
288+ ticker := time .NewTicker (500 * time .Millisecond )
289+ defer ticker .Stop ()
253290 for {
254- select {
255- case <- ctx .Done ():
256- return
257- default :
258- }
259291 if err := CheckProcessRunning (cmdBinary , cmd .Process .Pid ); err != nil {
260292 processErrChan <- err
261293 return
262294 }
263- // lets poll status every half second
264- time .Sleep (500 * time .Millisecond )
295+ select {
296+ case <- ctx .Done ():
297+ logrus .Debug ("VM waitForReadyFunc goroutine: ctx done" )
298+ return
299+ case <- ticker .C :
300+ }
265301 }
266302 }()
267303
@@ -279,7 +315,26 @@ func StartGenericAppleVM(mc *vmconfigs.MachineConfig, cmdBinary string, bootload
279315 }
280316 return nil
281317 }
282- return cmd .Process .Release , returnFunc , nil
318+
319+ // releaseCmdFunc is the callback that the caller of StartVM()
320+ // uses to release the resources associated with cmd.Process.
321+ // It's important to execute it after waitForReadyFunc completes,
322+ // otherwise cmd.Process methods won't work anymore.
323+ relCmdFunc := func () error {
324+ if err := cmd .Process .Release (); err != nil {
325+ logrus .Errorf ("error releasing VM Start command associated resources: %v" , err )
326+ }
327+ if ignitionSocket != nil {
328+ if err := ignitionSocket .Delete (); err != nil {
329+ logrus .Errorf ("unable to delete ignition socket: %v" , err )
330+ }
331+ }
332+ if err := readySocket .Delete (); err != nil {
333+ logrus .Errorf ("unable to delete ready socket: %v" , err )
334+ }
335+ return nil
336+ }
337+ return relCmdFunc , waitForReadyFunc , nil
283338}
284339
285340// CheckProcessRunning checks non blocking if the pid exited
0 commit comments