90 votes

Terminer un processus lancé avec os/exec dans Golang

Existe-t-il un moyen de terminer un processus démarré avec os.exec dans Golang ? Par exemple (à partir de http://golang.org/pkg/os/exec/#example_Cmd_Start ),

cmd := exec.Command("sleep", "5")
err := cmd.Start()
if err != nil {
    log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)

Existe-t-il un moyen d'interrompre ce processus à l'avance, par exemple après 3 secondes ?

Merci d'avance

161voto

Zippoxer Points 2271

Exécuter et terminer un exec.Process :

// Start a process:
cmd := exec.Command("sleep", "5")
if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

// Kill it:
if err := cmd.Process.Kill(); err != nil {
    log.Fatal("failed to kill process: ", err)
}

Exécuter et terminer un exec.Process après un temps mort :

ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
    // This will fail after 3 seconds. The 5 second sleep
    // will be interrupted.
}

Voir cet exemple dans le Go docs


L'héritage

Avant Go 1.7, nous n'avions pas la fonction context et cette réponse était différente.

Exécuter et terminer un exec.Process après un délai d'attente en utilisant des canaux et une goroutine :

// Start a process:
cmd := exec.Command("sleep", "5")
if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

// Wait for the process to finish or kill it after a timeout (whichever happens first):
done := make(chan error, 1)
go func() {
    done <- cmd.Wait()
}()
select {
case <-time.After(3 * time.Second):
    if err := cmd.Process.Kill(); err != nil {
        log.Fatal("failed to kill process: ", err)
    }
    log.Println("process killed as timeout reached")
case err := <-done:
    if err != nil {
        log.Fatalf("process finished with error = %v", err)
    }
    log.Print("process finished successfully")
}

Soit le processus se termine et son erreur (le cas échéant) est reçue par l'intermédiaire de done ou 3 secondes se sont écoulées et le programme est tué avant d'être terminé.

27voto

David Castillo Points 1461

Les autres réponses sont justes en ce qui concerne l'appel Kill() mais les parties concernant l'arrêt du processus après un délai d'attente sont un peu dépassées aujourd'hui.

Cela peut être fait maintenant avec la fonction context et exec.CommandContext (exemple adapté de l'exemple dans la documentation) :

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    defer cancel()

    if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
        // This will fail after 100 milliseconds. The 5 second sleep
        // will be interrupted.
    }
}

D'après les documents :

Le contexte fourni est utilisé pour tuer le processus (en appelant os.Process.Kill) si le contexte se termine avant que la commande ne se termine d'elle-même.

Après la Run() complète, vous pouvez inspecter ctx.Err() . Si le délai d'attente a été atteint, le type d'erreur renvoyé est le suivant DeadLineExceeded . Si c'est nil Vérifier l'état d'avancement du projet. err renvoyée par Run() pour voir si la commande s'est terminée sans erreur.

9voto

xiaoyi Points 3501

Une version plus simple sans sélection ni canaux.

func main() {
    cmd := exec.Command("cat", "/dev/urandom")
    cmd.Start()
    timer := time.AfterFunc(1*time.Second, func() {
        err := cmd.Process.Kill()
        if err != nil {
            panic(err) // panic as can't kill a process.
        }
    })
    err := cmd.Wait()
    timer.Stop()

    // read error from here, you will notice the kill from the 
    fmt.Println(err)
}

Après avoir consulté un programmeur expérimenté, il s'est avéré que ce n'était pas une façon assez "GO" de résoudre le problème. Veuillez donc vous référer à la réponse acceptée.


Voici une version encore plus courte et très simple. MAIS, il est possible d'avoir des tonnes de goroutines suspendues si le timeout est long.

func main() {
    cmd := exec.Command("cat", "/dev/urandom")
    cmd.Start()
    go func(){
        time.Sleep(timeout)
        cmd.Process.Kill()
    }()
    return cmd.Wait()
}

5voto

Jean Spector Points 496

Tandis que exec.CommandContext est très pratique et fonctionne bien dans la plupart des cas, j'ai eu quelques problèmes avec les enfants du processus qui restaient en vie - ce qui a eu pour conséquence que j'ai dû utiliser la fonction cmd.Wait() suspendu.

Si quelqu'un rencontre une situation similaire, voici comment j'ai résolu le problème.

  1. Demander la création d'un groupe de processus avant de lancer la commande à l'aide de Setpgid
  2. Commencez une routine d'aller qui tuera le groupe de processus en cas de dépassement de délai

Exemple naïf (pour plus de lisibilité) :

cmd := exec.Command("sleep", "5")

// Request the OS to assign process group to the new process, to which all its children will belong
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

go func() {
    time.Sleep(time.Second)
    // Send kill signal to the process group instead of single process (it gets the same value as the PID, only negative)
    syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) 
}

err := cmd.Run()
if err != nil {
    log.Fatal(err)
}
log.Printf("Command finished successfully")

Un exemple un peu plus beau (peut être moins intuitif pour les nouveaux Gophers) :

    // Create a context with timeout, which will close ctx.Done() channel upon timeout
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel() // Make sure the context is canceled, which will close ctx.Done() channel on function exit
    cmd := exec.Command("sleep", "5")

    // Request the OS to assign process group to the new process, to which all its children will belong
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

    go func() {
        // Wait until timeout or deferred cancellation
        <- ctx.Done()

        // Send kill signal to the process group instead of single process (it gets the same value as the PID, only negative)
        _ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
    }()

    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Command finished successfully")

P.S. Par souci de concision, j'ai remplacé cmd.Start + cmd.Wait con cmd.Run

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X