Signal Handling

Go menyediakan package os/signal untuk menangani sinyal sistem operasi seperti SIGTERM, SIGINT, dll. Ini penting untuk graceful shutdown dan manajemen resource.

Contoh Masalah

Bagaimana cara:

  1. Menangani sinyal sistem
  2. Implementasi graceful shutdown
  3. Cleanup resources
  4. Handle multiple signals

Penyelesaian

package main

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

// 1. Basic worker
type Worker struct {
    id     int
    done   chan struct{}
    logger *log.Logger
}

func NewWorker(id int) *Worker {
    return &Worker{
        id:     id,
        done:   make(chan struct{}),
        logger: log.New(os.Stdout, fmt.Sprintf("[Worker %d] ", id),
            log.Ltime),
    }
}

func (w *Worker) Start() {
    w.logger.Println("Starting worker...")
    go w.run()
}

func (w *Worker) Stop() {
    w.logger.Println("Stopping worker...")
    close(w.done)
}

func (w *Worker) run() {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            w.logger.Println("Working...")
        case <-w.done:
            w.logger.Println("Stopped")
            return
        }
    }
}

// 2. Resource manager
type ResourceManager struct {
    workers []*Worker
    server  *http.Server
    wg      sync.WaitGroup
    logger  *log.Logger
}

func NewResourceManager() *ResourceManager {
    return &ResourceManager{
        logger: log.New(os.Stdout, "[Manager] ", log.Ltime),
    }
}

func (rm *ResourceManager) StartWorkers(count int) {
    rm.logger.Printf("Starting %d workers...\n", count)
    for i := 0; i < count; i++ {
        worker := NewWorker(i + 1)
        rm.workers = append(rm.workers, worker)
        worker.Start()
    }
}

func (rm *ResourceManager) StartServer() {
    rm.logger.Println("Starting HTTP server...")
    
    // Create server
    rm.server = &http.Server{
        Addr: ":8080",
        Handler: http.HandlerFunc(func(w http.ResponseWriter,
            r *http.Request) {
            fmt.Fprintf(w, "Hello, World!")
        }),
    }

    // Start server in goroutine
    rm.wg.Add(1)
    go func() {
        defer rm.wg.Done()
        if err := rm.server.ListenAndServe(); err != http.ErrServerClosed {
            rm.logger.Printf("HTTP server error: %v\n", err)
        }
    }()
}

func (rm *ResourceManager) Shutdown(ctx context.Context) error {
    rm.logger.Println("Shutting down...")

    // Stop HTTP server
    if rm.server != nil {
        rm.logger.Println("Stopping HTTP server...")
        if err := rm.server.Shutdown(ctx); err != nil {
            return fmt.Errorf("server shutdown: %v", err)
        }
    }

    // Stop workers
    rm.logger.Println("Stopping workers...")
    for _, worker := range rm.workers {
        worker.Stop()
    }

    // Wait for all goroutines to finish
    rm.wg.Wait()
    return nil
}

// 3. Signal handler
type SignalHandler struct {
    signals     chan os.Signal
    rm          *ResourceManager
    logger      *log.Logger
    shutdownCtx context.Context
    cancel      context.CancelFunc
}

func NewSignalHandler(rm *ResourceManager) *SignalHandler {
    ctx, cancel := context.WithTimeout(context.Background(),
        10*time.Second)
    
    return &SignalHandler{
        signals:     make(chan os.Signal, 1),
        rm:          rm,
        logger:      log.New(os.Stdout, "[Signal] ", log.Ltime),
        shutdownCtx: ctx,
        cancel:      cancel,
    }
}

func (sh *SignalHandler) Start() {
    // Register for signals
    signal.Notify(sh.signals,
        syscall.SIGINT,
        syscall.SIGTERM,
        syscall.SIGHUP)

    // Handle signals
    go func() {
        for sig := range sh.signals {
            sh.logger.Printf("Received signal: %v\n", sig)
            
            switch sig {
            case syscall.SIGHUP:
                // Reload configuration
                sh.logger.Println("Reloading configuration...")
            case syscall.SIGINT, syscall.SIGTERM:
                // Graceful shutdown
                sh.logger.Println("Starting graceful shutdown...")
                
                if err := sh.rm.Shutdown(sh.shutdownCtx); err != nil {
                    sh.logger.Printf("Shutdown error: %v\n", err)
                }
                
                sh.cancel()
                return
            }
        }
    }()
}

func (sh *SignalHandler) Wait() {
    <-sh.shutdownCtx.Done()
    
    if err := sh.shutdownCtx.Err(); err == context.DeadlineExceeded {
        sh.logger.Println("Shutdown timed out")
    }
}

func main() {
    // Setup logging
    log.SetFlags(log.Ltime | log.Lmicroseconds)
    log.Println("Starting application...")

    // Create resource manager
    rm := NewResourceManager()

    // Start workers
    rm.StartWorkers(3)

    // Start HTTP server
    rm.StartServer()

    // Setup signal handling
    sh := NewSignalHandler(rm)
    sh.Start()

    log.Println("Application is ready")
    log.Println("Press Ctrl+C to shutdown")

    // Wait for shutdown
    sh.Wait()
    log.Println("Application stopped")
}

Penjelasan Kode

  1. Signal Types

    • SIGTERM
    • SIGINT
    • SIGHUP
  2. Graceful Shutdown

    • Context timeout
    • Resource cleanup
    • Wait groups
  3. Best Practices

    • Error handling
    • Timeout management
    • Resource management

Output

11:57:22 Starting application...
11:57:22 [Manager] Starting 3 workers...
11:57:22 [Worker 1] Starting worker...
11:57:22 [Worker 2] Starting worker...
11:57:22 [Worker 3] Starting worker...
11:57:22 [Manager] Starting HTTP server...
11:57:22 Application is ready
11:57:22 Press Ctrl+C to shutdown
11:57:23 [Worker 1] Working...
11:57:23 [Worker 2] Working...
11:57:23 [Worker 3] Working...
11:57:24 [Worker 1] Working...
11:57:24 [Worker 2] Working...
11:57:24 [Worker 3] Working...
^C
11:57:25 [Signal] Received signal: interrupt
11:57:25 [Signal] Starting graceful shutdown...
11:57:25 [Manager] Shutting down...
11:57:25 [Manager] Stopping HTTP server...
11:57:25 [Manager] Stopping workers...
11:57:25 [Worker 1] Stopping worker...
11:57:25 [Worker 2] Stopping worker...
11:57:25 [Worker 3] Stopping worker...
11:57:25 [Worker 1] Stopped
11:57:25 [Worker 2] Stopped
11:57:25 [Worker 3] Stopped
11:57:25 Application stopped

Tips

  • Handle sinyal yang relevan
  • Implementasi timeout
  • Cleanup semua resources
  • Log semua events
  • Test shutdown scenarios