signal.Notify în Go

Trimite semnalele primite de program (Ctrl+C, SIGTERM) pe un canal, ca să le tratezi; Stop oprește trimiterea.

Sintaxă
func Notify(c chan<- os.Signal, sig ...os.Signal)
func Stop(c chan<- os.Signal)   // Go 1.1

Ce face signal.Notify

Implicit, când apeși Ctrl+C (semnalul SIGINT, adică os.Interrupt) sau când sistemul trimite SIGTERM (de exemplu docker stop), programul Go se oprește imediat. Cu signal.Notify îi spui să pună semnalele pe un canal: programul nu se mai oprește singur și tu decizi ce faci (salvezi datele, închizi conexiunile, ieși).

Canalul trebuie să aibă buffer (de obicei make(chan os.Signal, 1)), pentru că Notify nu așteaptă: dacă nimeni nu e gata să primească, semnalul se pierde. Fără semnale în listă, sunt trimise toate.

signal.Stop(c) oprește trimiterea semnalelor pe canalul c; dacă niciun alt canal nu le mai așteaptă, semnalele revin la comportamentul implicit.

Parametri

Parametru Descriere
c Canalul pe care sosesc semnalele; cu buffer.
sig Semnalele urmărite: os.Interrupt, syscall.SIGTERM, syscall.SIGHUP etc.

Ce returnează

Nimic.

Exemple

Exemplul 1: Prinderea lui Ctrl+C

Programul își trimite singur semnalul de întrerupere, ca să-l putem vedea în rezultat.

package main

import (
    "fmt"
    "os"
    "os/signal"
)

func main() {
    semnale := make(chan os.Signal, 1)
    signal.Notify(semnale, os.Interrupt)

    fmt.Println("serverul rulează...")
    proces, _ := os.FindProcess(os.Getpid())
    proces.Signal(os.Interrupt) // ca și cum ai apăsa Ctrl+C

    s := <-semnale
    fmt.Println("am primit:", s)
    fmt.Println("salvez datele și închid frumos")
}

Rezultat

serverul rulează...
am primit: interrupt
salvez datele și închid frumos

Exemplul 2: Tratarea mai multor semnale

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    semnale := make(chan os.Signal, 1)
    signal.Notify(semnale, syscall.SIGHUP, syscall.SIGTERM)

    proces, _ := os.FindProcess(os.Getpid())
    for _, trimis := range []os.Signal{syscall.SIGHUP, syscall.SIGTERM} {
        proces.Signal(trimis)
        switch <-semnale {
        case syscall.SIGHUP:
            fmt.Println("SIGHUP: recitesc configurarea")
        case syscall.SIGTERM:
            fmt.Println("SIGTERM: opresc serverul")
        }
    }
}

Rezultat

SIGHUP: recitesc configurarea
SIGTERM: opresc serverul

Exemplul 3: signal.Stop

După Stop, semnalul SIGWINCH (schimbarea mărimii terminalului, ignorat implicit) nu mai ajunge pe canal.

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGWINCH)
    proces, _ := os.FindProcess(os.Getpid())

    proces.Signal(syscall.SIGWINCH)
    fmt.Println("primit:", <-c)

    signal.Stop(c)
    proces.Signal(syscall.SIGWINCH)
    select {
    case s := <-c:
        fmt.Println("primit din nou:", s)
    case <-time.After(200 * time.Millisecond):
        fmt.Println("după Stop: niciun semnal pe canal")
    }
}

Rezultat

primit: window size changes
după Stop: niciun semnal pe canal

Greșeli frecvente

  • Folosești un canal fără buffer: make(chan os.Signal). Dacă semnalul vine când nu aștepți pe canal, se pierde. go vet avertizează pentru asta.
  • Încerci să prinzi SIGKILL sau SIGSTOP. Aceste semnale nu pot fi prinse; sistemul oprește procesul oricum.
  • Apelezi Notify și uiți să ieși din program după tratare. Ctrl+C nu mai oprește programul, iar utilizatorul trebuie să folosească kill -9.

Comenzi înrudite

  • signal.NotifyContext: Returnează un context care se anulează când programul primește unul dintre semnalele date (de exemplu Ctrl+C).
  • os.Exit: Oprește imediat programul cu codul de ieșire dat; funcțiile amânate cu defer nu mai rulează.
  • exec.CommandContext: Ca exec.Command, dar procesul este oprit automat când contextul expiră sau este anulat.