Skip to content
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

add wait flag to stop command #1384

Closed
wants to merge 2 commits into from
Closed
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
24 changes: 18 additions & 6 deletions cmd/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"fmt"
"os"
"path/filepath"
"time"

"github.com/spf13/cobra"

Expand All @@ -26,8 +27,10 @@ import (
)

var (
stopAppID string
stopK8s bool
stopAppID string
stopK8s bool
stopWait bool
stopTimeout int
)

var StopCmd = &cobra.Command{
Expand All @@ -48,17 +51,24 @@ dapr stop --run-file dapr.yaml -k

# Stop and delete Kubernetes deployment of multiple apps by providing a directory path containing the run config file(dapr.yaml)
dapr stop --run-file /path/to/directory -k

# Stop and wait for Dapr application to exit
dapr stop --app-id <ID> --wait --timeout 20
`,
Run: func(cmd *cobra.Command, args []string) {
var err error
var timeout time.Duration
if stopWait {
timeout = time.Duration(int(time.Second) * stopTimeout)
}
if len(runFilePath) > 0 {
runFilePath, err = getRunFilePath(runFilePath)
if err != nil {
print.FailureStatusEvent(os.Stderr, "Failed to get run file path: %v", err)
os.Exit(1)
}
if !stopK8s {
err = executeStopWithRunFile(runFilePath)
err = executeStopWithRunFile(runFilePath, timeout)
if err != nil {
print.FailureStatusEvent(os.Stderr, "Failed to stop Dapr and app processes: %s", err)
} else {
Expand All @@ -85,7 +95,7 @@ dapr stop --run-file /path/to/directory -k
}
cliPIDToNoOfApps := standalone.GetCLIPIDCountMap(apps)
for _, appID := range args {
err = standalone.Stop(appID, cliPIDToNoOfApps, apps)
err = standalone.Stop(appID, cliPIDToNoOfApps, apps, timeout)
if err != nil {
print.FailureStatusEvent(os.Stderr, "failed to stop app id %s: %s", appID, err)
} else {
Expand All @@ -99,14 +109,16 @@ func init() {
StopCmd.Flags().StringVarP(&stopAppID, "app-id", "a", "", "The application id to be stopped")
StopCmd.Flags().StringVarP(&runFilePath, "run-file", "f", "", "Path to the run template file for the list of apps to stop")
StopCmd.Flags().BoolVarP(&stopK8s, "kubernetes", "k", false, "Stop deployments in Kunernetes based on multi-app run file")
StopCmd.Flags().BoolVarP(&stopWait, "wait", "w", false, "Wait for apps to stop")
StopCmd.Flags().IntVarP(&stopTimeout, "timeout", "t", 15, "Wait timeout in seconds")
StopCmd.Flags().BoolP("help", "h", false, "Print this help message")
RootCmd.AddCommand(StopCmd)
}

func executeStopWithRunFile(runFilePath string) error {
func executeStopWithRunFile(runFilePath string, timeout time.Duration) error {
absFilePath, err := filepath.Abs(runFilePath)
if err != nil {
return fmt.Errorf("failed to get absolute file path for %s: %w", runFilePath, err)
}
return standalone.StopAppsWithRunFile(absFilePath)
return standalone.StopAppsWithRunFile(absFilePath, timeout)
}
59 changes: 50 additions & 9 deletions pkg/standalone/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,42 @@ limitations under the License.
package standalone

import (
"errors"
"fmt"
"os"
"syscall"
"time"

"github.com/dapr/cli/utils"
)

// Stop terminates the application process.
func Stop(appID string, cliPIDToNoOfApps map[int]int, apps []ListOutput) error {
func Stop(appID string, cliPIDToNoOfApps map[int]int, apps []ListOutput, timeout time.Duration) error {
for _, a := range apps {
if a.AppID == appID {
var pid string
var pid int
// Kill the Daprd process if Daprd was started without CLI, otherwise
// kill the CLI process which also kills the associated Daprd process.
if a.CliPID == 0 || cliPIDToNoOfApps[a.CliPID] > 1 {
pid = fmt.Sprintf("%v", a.DaprdPID) //nolint: perfsprint
pid = a.DaprdPID //nolint: perfsprint
cliPIDToNoOfApps[a.CliPID]--
} else {
pid = fmt.Sprintf("%v", a.CliPID) //nolint: perfsprint
pid = a.CliPID //nolint: perfsprint
}

_, err := utils.RunCmdAndWait("kill", pid)
_, err := utils.RunCmdAndWait("kill", fmt.Sprintf("%v", pid)) //nolint:perfsprint
if err != nil {
return err
}

return err
return waitForProccessToExit(pid, timeout)
}
}
return fmt.Errorf("couldn't find app id %s", appID)
}

// StopAppsWithRunFile terminates the daprd and application processes with the given run file.
func StopAppsWithRunFile(runTemplatePath string) error {
func StopAppsWithRunFile(runTemplatePath string, timeout time.Duration) error {
apps, err := List()
if err != nil {
return err
Expand All @@ -58,12 +64,47 @@ func StopAppsWithRunFile(runTemplatePath string) error {
if err != nil {
// Fall back to cliPID if pgid is not available.
_, err = utils.RunCmdAndWait("kill", fmt.Sprintf("%v", a.CliPID)) //nolint:perfsprint
return err
if err != nil {
return err
}
return waitForProccessToExit(a.CliPID, timeout)
}
// Kill the whole process group.
err = syscall.Kill(-pgid, syscall.SIGINT)
return err
if err != nil {
return err
}
return waitForProccessToExit(-pgid, timeout)
}
}
return fmt.Errorf("couldn't find apps with run file %q", runTemplatePath)
}

func waitForProccessToExit(pid int, timeout time.Duration) error {
if timeout == 0 {
return nil
}

proc, err := os.FindProcess(pid)
if err != nil {
return nil //nolint:nilerr
}

ticker := time.NewTicker(time.Second)
timer := time.NewTimer(timeout)
defer timer.Stop()

for {
select {
case <-ticker.C:
if err := proc.Signal(syscall.Signal(0)); err != nil && !errors.Is(err, os.ErrProcessDone) {
return err
} else if err != nil {
return nil //nolint:nilerr
}
case <-timer.C:
proc.Signal(syscall.SIGKILL)
return nil
}
}
}