web-dev-qa-db-fra.com

Comment quitter un programme go honorant les appels différés?

Je dois utiliser defer pour libérer les allocations créées manuellement à l'aide de la bibliothèque C, mais je dois également os.Exit avec un état non 0 à un moment donné. La partie délicate est que os.Exit ignore toute instruction différée:

package main

import "fmt"
import "os"

func main() {

    // `defer`s will _not_ be run when using `os.Exit`, so
    // this `fmt.Println` will never be called.
    defer fmt.Println("!")
    // sometimes ones might use defer to do critical operations
    // like close a database, remove a lock or free memory

    // Exit with status code.
    os.Exit(3)
}

Aire de jeux: http://play.golang.org/p/CDiAh9SXRM volé à https://gobyexample.com/exit

Alors, comment quitter un programme go honorant les appels defer déclarés? Existe-t-il une alternative à os.Exit?

50
marcio

runtime.Goexit() est le moyen facile d'accomplir cela.

Goexit termine le goroutine qui l'appelle. Aucun autre goroutine n'est affecté. Goexit exécute tous les appels différés avant de terminer le goroutine. Parce que Goexit n'est pas une panique, cependant, tout appel de récupération dans ces fonctions différées retournera zéro.

Pourtant:

L'appel de Goexit à partir du goroutine principal met fin à ce goroutine sans retour de la fonction principale. Puisque func main n'est pas revenu, le programme continue l'exécution d'autres goroutines. Si tous les autres goroutines sortent, le programme plante.

Donc, si vous l'appelez depuis le goroutine principal, en haut de main vous devez ajouter

defer os.Exit(0)

En dessous, vous voudrez peut-être ajouter d'autres instructions defer qui informent les autres goroutines de s'arrêter et de nettoyer.

19
EMBLEM

Faites simplement descendre votre programme d'un niveau et retournez votre code de sortie:

package main

import "fmt"
import "os"

func doTheStuff() int {
    defer fmt.Println("!")

    return 3
}

func main() {
    os.Exit(doTheStuff())
}
30
Rob Napier

Après quelques recherches, référez-vous à ceci this , j'ai trouvé une alternative qui:

Nous pouvons profiter de panic et recover. Il s'avère que panic, par nature, honorera les appels defer mais se terminera toujours avec non 0 code d'état et vidage d'une trace de pile. L'astuce est que nous pouvons remplacer le dernier aspect du comportement de panique avec:

package main

import "fmt"
import "os"

type Exit struct{ Code int }

// exit code handler
func handleExit() {
    if e := recover(); e != nil {
        if exit, ok := e.(Exit); ok == true {
            os.Exit(exit.Code)
        }
        panic(e) // not an Exit, bubble up
    }
}

Maintenant, pour quitter un programme à tout moment et conserver toute instruction defer déclarée, il nous suffit d'émettre un type Exit:

func main() {
    defer handleExit() // plug the exit handler
    defer fmt.Println("cleaning...")
    panic(Exit{3}) // 3 is the exit code
}

Il ne nécessite aucune refactorisation à part le fait de brancher une ligne à l'intérieur de func main:

func main() {
    defer handleExit()
    // ready to go
}

Cela évolue assez bien avec des bases de code plus grandes, je vais donc le laisser disponible pour examen. J'espère que cela aide.

Aire de jeux: http://play.golang.org/p/4tyWwhcX0-

22
marcio

Pour la postérité, c'était pour moi une solution plus élégante:

func main() { 
    retcode := 0
    defer func() { os.Exit(retcode) }()
    defer defer1()
    defer defer2()

    [...]

    if err != nil {
        retcode = 1
        return
    }
}