signal.NotifyContext în Go

Returnează un context care se anulează când programul primește unul dintre semnalele date (de exemplu Ctrl+C).

Sintaxă
func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)

Ce face signal.NotifyContext

signal.NotifyContext combină semnalele cu context: contextul returnat se închide (ctx.Done()) la primul semnal din listă. Îl dai mai departe funcțiilor care acceptă context (servere HTTP, interogări de baze de date, exec.CommandContext) și toate se opresc ordonat la Ctrl+C.

Funcția stop anulează urmărirea semnalelor și redă comportamentul implicit; apeleaz-o cu defer stop(). După primul semnal, un al doilea Ctrl+C oprește programul abia după ce ai apelat stop. Din Go 1.26, context.Cause(ctx) spune ce semnal a fost primit.

Parametri

Parametru Descriere
parent Contextul părinte, de obicei context.Background().
signals Semnalele care anulează contextul.

Ce returnează

Contextul nou și funcția stop.

Exemple

Exemplul 1: Oprire ordonată a unei bucle de lucru

După 120 ms programul își trimite singur os.Interrupt, ca și cum utilizatorul ar apăsa Ctrl+C.

package main

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

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()

    go func() {
        time.Sleep(120 * time.Millisecond)
        proces, _ := os.FindProcess(os.Getpid())
        proces.Signal(os.Interrupt)
    }()

    lucrari := 0
    for {
        select {
        case <-ctx.Done():
            fmt.Println("oprire cerută:", context.Cause(ctx))
            fmt.Println("a lucrat înainte de oprire:", lucrari > 0)
            return
        case <-time.After(50 * time.Millisecond):
            lucrari++
        }
    }
}

Rezultat

oprire cerută: interrupt signal received
a lucrat înainte de oprire: true

Exemplul 2: Server HTTP oprit cu Ctrl+C

Model complet pentru un server real; nu rulează aici pentru că așteaptă conexiuni.

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    srv := &http.Server{Addr: ":8080"}
    go func() {
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatal(err)
        }
    }()
    log.Println("server pornit pe :8080")

    <-ctx.Done()
    log.Println("opresc serverul...")
    ctxOprire, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    srv.Shutdown(ctxOprire)
    log.Println("gata")
}

Rezultat în terminal (după Ctrl+C)

2025/03/14 10:00:00 server pornit pe :8080
^C2025/03/14 10:02:17 opresc serverul...
2025/03/14 10:02:17 gata

Greșeli frecvente

  • Uiți defer stop(). Semnalele rămân redirecționate și Ctrl+C nu mai oprește programul nici după ce ai terminat.
  • Folosești pentru oprirea serverului același context care tocmai a fost anulat. Shutdown are nevoie de un context nou, cu timpul lui de grație.

Comenzi înrudite

  • signal.Notify: Trimite semnalele primite de program (Ctrl+C, SIGTERM) pe un canal, ca să le tratezi; Stop oprește trimiterea.
  • exec.CommandContext: Ca exec.Command, dar procesul este oprit automat când contextul expiră sau este anulat.
  • os.Exit: Oprește imediat programul cu codul de ieșire dat; funcțiile amânate cu defer nu mai rulează.