Можно ли перехватить сигнал Ctrl + C и запустить функцию очистки «отложенным» способом?

207

Я хочу захватить сигнал Ctrl+C( SIGINT), отправленный с консоли, и распечатать некоторые частичные итоги прогона.

Возможно ли это на Голанге?

Примечание: Когда я впервые опубликовал вопрос, я был озадачен тем, Ctrl+Cчто SIGTERMвместо того, чтобы быть SIGINT.

Себастьян Гриньоли
источник

Ответы:

261

Вы можете использовать пакет os / signal для обработки входящих сигналов. Ctrl+ Cэто SIGINT , так что вы можете использовать это для ловушки 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
    }
}()

То, как вы заставляете свою программу завершать работу и печатать информацию, полностью зависит от вас.

Лили Баллард
источник
Спасибо! Итак ... ^ C это не SIGTERM, тогда? ОБНОВЛЕНИЕ: Извините, ссылка, которую вы предоставили, достаточно подробная!
Себастьян Гриньоли
19
Вместо этого for sig := range g {вы также можете использовать, <-sigchanкак и в предыдущем ответе: stackoverflow.com/questions/8403862/…
Денис Сегюре
3
@dystroy: Конечно, если вы действительно собираетесь завершить программу в ответ на первый сигнал. Используя цикл, вы можете перехватить все сигналы, если вы решили не прерывать программу.
Лили Баллард
79
Примечание: вы должны на самом деле собрать программу, чтобы это работало. Если вы запускаете программу через go runконсоль и отправляете SIGTERM через ^ C, сигнал записывается в канал, и программа отвечает, но, похоже, неожиданно выпадает из цикла. Это потому, что СИГРЕРМ тоже идет go run! (Это вызвало у меня существенное замешательство!)
Уильям Перселл
5
Обратите внимание, что для того, чтобы программа могла получить процессорное время для обработки сигнала, основная программа должна вызвать блокирующую операцию или вызов runtime.Goschedв соответствующем месте (в главном цикле вашей программы, если он есть)
misterbee
107

Это работает:

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
    }
}

источник
1
Для других читателей: посмотрите на ответ @adamonduty, чтобы узнать, почему вы хотите поймать os.Interrupt и syscall.SIGTERM. Было бы неплохо включить его объяснение в этот ответ, тем более что он опубликовал несколько месяцев до вас.
Chris
1
Почему вы используете неблокирующий канал? Это необходимо?
Аун
4
@ Барри, почему размер буфера 2 вместо 1?
Пдева
2
Вот выдержка из документации . «Пакетный сигнал не будет блокировать отправку на c: вызывающий должен убедиться, что c имеет достаточно места в буфере, чтобы не отставать от ожидаемой скорости сигнала. Для канала, используемого для уведомления только об одном значении сигнала, достаточно буфера размера 1».
bmdelacruz
25

Чтобы немного добавить к другим ответам, если вы действительно хотите перехватить SIGTERM (сигнал по умолчанию, посылаемый командой kill), вы можете использовать syscall.SIGTERMвместо os.Interrupt. Помните, что интерфейс системного вызова зависит от системы и может работать не везде (например, в Windows). Но это хорошо работает, чтобы поймать оба:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
adamlamar
источник
7
signal.NotifyФункция позволяет указать несколько сигналов одновременно. Таким образом, вы можете упростить свой код до signal.Notify(c, os.Interrupt, syscall.SIGTERM).
Йохен
Я думаю, что узнал об этом после публикации. Исправлена!
adamlamar
2
Как насчет os.Kill?
Аун
2
@ Затмение Отличный вопрос! os.Kill соответствует к syscall.Kill, который является сигналом , который может быть послан , но не пойман. Это эквивалентно команде kill -9 <pid>. Если вы хотите, чтобы ловить kill <pid>и изящно выключить, вы должны использовать syscall.SIGTERM.
adamlamar
@adamlamar Ааа, это имеет смысл. Спасибо!
Аун
18

Были (на момент публикации) один или два небольших опечаток в принятом ответе выше, так что вот исправленная версия. В этом примере я останавливаю профилировщик процессора при получении 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)                                                     
  }                                                                
}()    
Gravitron
источник
2
Обратите внимание, что для того, чтобы программа могла получить процессорное время для обработки сигнала, основная программа должна вызвать блокирующую операцию или вызов runtime.Goschedв соответствующем месте (в главном цикле вашей программы, если он есть)
misterbee
0

Death - это простая библиотека, которая использует каналы и группу ожидания для ожидания сигналов выключения. Как только сигнал будет получен, он вызовет метод close для всех ваших структур, которые вы хотите очистить.

Бен Олдрич
источник
3
Так много строк кода и зависимость от внешней библиотеки, что можно сделать в четырех строках кода? (согласно принятому ответу)
Джейкоб
это позволяет вам выполнять очистку всех из них параллельно и автоматически закрывать структуры, если они имеют стандартный интерфейс закрытия.
Бен Олдрич
0

У вас может быть другая процедура, которая обнаруживает сигналы syscall.SIGINT и syscall.SIGTERM и передает их на канал с помощью signal.Notify . Вы можете отправить ловушку в эту программу с помощью канала и сохранить ее в срезе функции. Когда сигнал отключения обнаружен на канале, вы можете выполнить эти функции в срезе. Это можно использовать для очистки ресурсов, ожидания завершения выполнения процедур, сохранения данных или печати частичных итогов выполнения.

Я написал небольшую и простую утилиту для добавления и запуска хуков при выключении. Надеюсь, что это может помочь.

https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go

Вы можете сделать это «отложенным» способом.

пример для корректного завершения работы сервера:

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()
Анкит Арора
источник
0

Это еще одна версия, которая работает, если у вас есть задачи для очистки. Код оставит процесс очистки в их методе.

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
}

в случае, если вам нужно очистить основную функцию, вам нужно перехватить сигнал в основном потоке, используя go func ().

HLEX
источник
-2

Просто для записи, если кому-то нужен способ обработки сигналов в Windows. У меня было требование обрабатывать из prog A вызывающий prog B через os / exec, но prog B никогда не мог завершиться изящно, поскольку отправлял сигналы через ex. cmd.Process.Signal (syscall.SIGTERM) или другие сигналы не поддерживаются в Windows. Я справился с этим, создав временный файл в качестве сигнала ex. .signal.term через prog A и prog B необходимо проверить, существует ли этот файл на основе интервала, если файл существует, он выйдет из программы и при необходимости выполнит очистку, я уверен, что есть и другие способы, но это помогло.

user212806
источник