Quero capturar o sinal Ctrl+C( SIGINT
) enviado do console e imprimir alguns totais parciais de execução.
Isso é possível em Golang?
Nota: Quando publiquei a pergunta pela primeira vez, fiquei confuso sobre Ctrl+Cestar em SIGTERM
vez de SIGINT
.
Quero capturar o sinal Ctrl+C( SIGINT
) enviado do console e imprimir alguns totais parciais de execução.
Isso é possível em Golang?
Nota: Quando publiquei a pergunta pela primeira vez, fiquei confuso sobre Ctrl+Cestar em SIGTERM
vez de SIGINT
.
Respostas:
Você pode usar o pacote os / signal para lidar com os sinais recebidos. Ctrl+ Cé SIGINT , então você pode usá-lo para interceptar os.Interrupt
.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(){
for sig := range c {
// sig is a ^C, handle it
}
}()
A maneira como você faz com que o seu programa encerre e imprima as informações é inteiramente sua.
for sig := range g {
, você também pode usar <-sigchan
como nesta resposta anterior: stackoverflow.com/questions/8403862/…
go run
em um console e enviar um SIGTERM via ^ C, o sinal será gravado no canal e o programa responderá, mas parecerá sair inesperadamente do loop. Isso ocorre porque o SIGRERM também vai go run
! (Isso tem me causa confusão substancial!)
runtime.Gosched
em local apropriado (em loop principal do programa, se ele tiver um)
Isso funciona:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time" // or "runtime"
)
func cleanup() {
fmt.Println("cleanup")
}
func main() {
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cleanup()
os.Exit(1)
}()
for {
fmt.Println("sleeping...")
time.Sleep(10 * time.Second) // or runtime.Gosched() or similar per @misterbee
}
}
Para adicionar um pouco às outras respostas, se você realmente deseja capturar o SIGTERM (o sinal padrão enviado pelo comando kill), pode usar syscall.SIGTERM
no lugar do os.Interrupt. Cuidado que a interface syscall é específica do sistema e pode não funcionar em qualquer lugar (por exemplo, no Windows). Mas funciona bem para capturar os dois:
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
signal.Notify
função permite especificar vários sinais ao mesmo tempo. Assim, você pode simplificar seu código para signal.Notify(c, os.Interrupt, syscall.SIGTERM)
.
os.Kill
corresponde a syscall.Kill
, que é um sinal que pode ser enviado, mas não capturado. É equivalente ao comando kill -9 <pid>
. Se você deseja capturar kill <pid>
e desligar normalmente, você deve usar syscall.SIGTERM
.
Havia (no momento da publicação) um ou dois pequenos erros de digitação na resposta aceita acima, então aqui está a versão limpa. Neste exemplo, paro o criador de perfil da CPU ao receber Ctrl+ C.
// capture ctrl+c and stop CPU profiler
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
log.Printf("captured %v, stopping profiler and exiting..", sig)
pprof.StopCPUProfile()
os.Exit(1)
}
}()
runtime.Gosched
em local apropriado (em loop principal do programa, se ele tiver um)
Todas as opções acima parecem funcionar quando emendadas, mas a página de sinais do gobyexample tem um exemplo realmente limpo e completo de captura de sinal. Vale a pena adicionar a esta lista.
A morte é uma biblioteca simples que usa canais e um grupo de espera para aguardar sinais de desligamento. Depois que o sinal for recebido, ele chamará um método close em todas as suas estruturas que você deseja limpar.
Você pode ter uma goroutine diferente que detecte os sinais syscall.SIGINT e syscall.SIGTERM e retransmiti-los para um canal usando signal.Notify . Você pode enviar um gancho para essa goroutine usando um canal e salvá-lo em uma fatia de função. Quando o sinal de desligamento é detectado no canal, você pode executar essas funções na fatia. Isso pode ser usado para limpar os recursos, aguardar a conclusão de goroutines em execução, persistir dados ou imprimir totais parciais de execução.
Eu escrevi um utilitário pequeno e simples para adicionar e executar ganchos no desligamento. Espero que possa ser de ajuda.
https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go
Você pode fazer isso de uma maneira "adiada".
exemplo para desligar um servidor normalmente:
srv := &http.Server{}
go_shutdown_hook.ADD(func() {
log.Println("shutting down server")
srv.Shutdown(nil)
log.Println("shutting down server-done")
})
l, err := net.Listen("tcp", ":3090")
log.Println(srv.Serve(l))
go_shutdown_hook.Wait()
Esta é outra versão que funciona caso você tenha algumas tarefas para limpar. O código deixará o processo de limpeza em seu método.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
_,done1:=doSomething1()
_,done2:=doSomething2()
//do main thread
println("wait for finish")
<-done1
<-done2
fmt.Print("clean up done, can exit safely")
}
func doSomething1() (error, chan bool) {
//do something
done:=make(chan bool)
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
//cleanup of something1
done<-true
}()
return nil,done
}
func doSomething2() (error, chan bool) {
//do something
done:=make(chan bool)
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
//cleanup of something2
done<-true
}()
return nil,done
}
caso você precise limpar a função principal, você precisa capturar o sinal na thread principal usando go func () também.
Só para constar se alguém precisa de uma maneira de lidar com sinais no Windows. Eu tinha um requisito para lidar com prog A chamando prog B através de os / exec, mas prog B nunca foi capaz de terminar normalmente porque enviando sinais através de ex. O cmd.Process.Signal (syscall.SIGTERM) ou outros sinais não são suportados no Windows. A maneira como lidei com isso é criando um arquivo temporário como um sinal ex. .signal.term através de prog A e prog B precisa verificar se esse arquivo existe na base do intervalo; se existir, ele sairá do programa e executará uma limpeza, se necessário, tenho certeza de que existem outras maneiras, mas isso foi útil.