OS Processes, Exec, Signals, and Exit in Go
Systems software and container workloads constantly interface with the host operating system: launching child processes, piping data between subprocesses, reacting to orchestration termination signals (like Kubernetes SIGTERM), and cleanly terminating.
1. Spawning Processes (os/exec)
The os/exec package runs external commands. It inspects PATH, sets up stdin/stdout/stderr pipes, and captures exit codes.
Basic Command Execution and Output
package main
import (
"context"
"fmt"
"os/exec"
"time"
)
func main() {
// 1. Simple command execution capturing stdout
cmd := exec.Command("echo", "hello", "from", "os/exec")
out, err := cmd.Output()
if err != nil {
panic(err)
}
fmt.Printf("Output: %s", string(out))
// 2. Running command with timeout via context
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// If the command does not complete within 2 seconds, context cancels and kills the process
pingCmd := exec.CommandContext(ctx, "sleep", "1")
if err := pingCmd.Run(); err != nil {
fmt.Printf("Command failed or timed out: %v\n", err)
} else {
fmt.Println("Sleep completed within timeout")
}
}Piping Data into and out of Subprocesses
package main
import (
"bytes"
"fmt"
"os/exec"
"strings"
)
func main() {
// Grep command reading from an in-memory buffer
cmd := exec.Command("grep", "cloud")
cmd.Stdin = strings.NewReader("microservice architecture\ncloud-native platform\nmonolith app\n")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
panic(err)
}
fmt.Println("Filtered output:")
fmt.Print(stdout.String())
}2. Replacing the Current Process (syscall.Exec)
In Unix-like systems, sometimes you do not want a child process; you want the current Go program to replace itself with another executable image (retaining PID). Go provides syscall.Exec for this purpose.
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
)
func main() {
// Find binary path
binary, err := exec.LookPath("ls")
if err != nil {
panic(err)
}
// Arguments: argv[0] is typically program name
args := []string{"ls", "-la", "/tmp"}
// Environment variables to forward
env := os.Environ()
fmt.Println("Replacing current Go process with 'ls -la /tmp'...")
// syscall.Exec NEVER returns on success.
// The current process memory is completely overwritten.
execErr := syscall.Exec(binary, args, env)
if execErr != nil {
// Only reached if exec fails (e.g. file not found or permission denied)
panic(execErr)
}
}3. Signal Handling (os/signal)
Kubernetes and container runtimes send SIGTERM when shutting down pods, followed by SIGKILL after a grace period. Go programs must trap SIGTERM and SIGINT (Ctrl+C) to finish ongoing requests before quitting.
Idiomatic Signal Handling with signal.NotifyContext
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// signal.NotifyContext returns a context that is marked Done when any of the specified signals arrive
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
fmt.Println("Worker running. Press Ctrl+C or send SIGTERM to stop...")
select {
case <-ctx.Done():
fmt.Println("\nShutdown signal received! Cleaning up resources...")
// Perform graceful drain
time.Sleep(500 * time.Millisecond)
fmt.Println("Drained all connections cleanly.")
}
}4. Program Exit (os.Exit)
os.Exit terminates the program immediately with the specified integer status code (0 for success, non-zero for error).
WARNING
Defers do NOT run on
os.Exit!
Whenos.Exit()is called, deferred functions are bypassed completely. Always allowmain()to return naturally when possible, or perform cleanup before invokingos.Exit.
package main
import (
"fmt"
"os"
)
func main() {
// This deferred function will NEVER execute due to os.Exit below!
defer fmt.Println("This defer will NOT be printed!")
fmt.Println("Starting validation check...")
isValid := false
if !isValid {
fmt.Println("Fatal validation error: exiting with code 2")
os.Exit(2)
}
fmt.Println("Validation passed!")
}Related Topics
- CLI, Environment, and Logging — Arguments, flags, and slog.
- HTTP and Graceful Shutdown — Graceful server drain with contexts and signals.
- Files, Directories, and Embed — File operations and temp paths.