Skip to content

cmd: add support for restarting a running instance #3323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 7, 2025
Merged
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
1 change: 1 addition & 0 deletions cmd/limactl/main.go
Original file line number Diff line number Diff line change
@@ -159,6 +159,7 @@ func newApp() *cobra.Command {
newUnprotectCommand(),
newTunnelCommand(),
newTemplateCommand(),
newRestartCommand(),
)
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
rootCmd.AddCommand(startAtLoginCommand())
52 changes: 52 additions & 0 deletions cmd/limactl/restart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"github.com/lima-vm/lima/pkg/instance"
"github.com/lima-vm/lima/pkg/store"
"github.com/spf13/cobra"
)

func newRestartCommand() *cobra.Command {
restartCmd := &cobra.Command{
Use: "restart INSTANCE",
Short: "Restart a running instance",
Args: WrapArgsError(cobra.MaximumNArgs(1)),
RunE: restartAction,
ValidArgsFunction: restartBashComplete,
GroupID: basicCommand,
}

restartCmd.Flags().BoolP("force", "f", false, "force stop and restart the instance")
return restartCmd
}

func restartAction(cmd *cobra.Command, args []string) error {
instName := DefaultInstanceName
if len(args) > 0 {
instName = args[0]
}

inst, err := store.Inspect(instName)
if err != nil {
return err
}

force, err := cmd.Flags().GetBool("force")
if err != nil {
return err
}

ctx := cmd.Context()
if force {
return instance.RestartForcibly(ctx, inst)
}

return instance.Restart(ctx, inst)
}

func restartBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return bashCompleteInstanceNames(cmd)
}
2 changes: 1 addition & 1 deletion cmd/limactl/stop.go
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ func stopAction(cmd *cobra.Command, args []string) error {
if force {
instance.StopForcibly(inst)
} else {
err = instance.StopGracefully(inst)
err = instance.StopGracefully(inst, false)
}
// TODO: should we also reconcile networks if graceful stop returned an error?
if err == nil {
45 changes: 45 additions & 0 deletions pkg/instance/restart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package instance

import (
"context"

networks "github.com/lima-vm/lima/pkg/networks/reconcile"
"github.com/lima-vm/lima/pkg/store"
"github.com/sirupsen/logrus"
)

const launchHostAgentForeground = false

func Restart(ctx context.Context, inst *store.Instance) error {
if err := StopGracefully(inst, true); err != nil {
return err
}

if err := networks.Reconcile(ctx, inst.Name); err != nil {
return err
}

if err := Start(ctx, inst, "", launchHostAgentForeground); err != nil {
return err
}

return nil
}

func RestartForcibly(ctx context.Context, inst *store.Instance) error {
logrus.Info("Restarting the instance forcibly")
StopForcibly(inst)

if err := networks.Reconcile(ctx, inst.Name); err != nil {
return err
}

if err := Start(ctx, inst, "", launchHostAgentForeground); err != nil {
return err
}

return nil
}
39 changes: 37 additions & 2 deletions pkg/instance/stop.go
Original file line number Diff line number Diff line change
@@ -19,8 +19,12 @@ import (
"github.com/sirupsen/logrus"
)

func StopGracefully(inst *store.Instance) error {
func StopGracefully(inst *store.Instance, isRestart bool) error {
if inst.Status != store.StatusRunning {
if isRestart {
logrus.Warn("The instance is not running, continuing with the restart")
return nil
}
return fmt.Errorf("expected status %q, got %q (maybe use `limactl stop -f`?)", store.StatusRunning, inst.Status)
}

@@ -31,7 +35,13 @@ func StopGracefully(inst *store.Instance) error {
}

logrus.Info("Waiting for the host agent and the driver processes to shut down")
return waitForHostAgentTermination(context.TODO(), inst, begin)
err := waitForHostAgentTermination(context.TODO(), inst, begin)
if err != nil {
return err
}

logrus.Info("Waiting for the instance to shut down")
return waitForInstanceShutdown(context.TODO(), inst)
}

func waitForHostAgentTermination(ctx context.Context, inst *store.Instance, begin time.Time) error {
@@ -64,6 +74,31 @@ func waitForHostAgentTermination(ctx context.Context, inst *store.Instance, begi
return nil
}

func waitForInstanceShutdown(ctx context.Context, inst *store.Instance) error {
ctx2, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()

ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()

for {
select {
case <-ticker.C:
updatedInst, err := store.Inspect(inst.Name)
if err != nil {
return errors.New("failed to inspect instance status: " + err.Error())
}

if updatedInst.Status == store.StatusStopped {
logrus.Infof("The instance %s has shut down", updatedInst.Name)
return nil
}
case <-ctx2.Done():
return errors.New("timed out waiting for instance to shut down after 3 minutes")
}
}
}

func StopForcibly(inst *store.Instance) {
if inst.DriverPID > 0 {
logrus.Infof("Sending SIGKILL to the %s driver process %d", inst.VMType, inst.DriverPID)