Operating System Integration

Why This Matters

Consider building a production web server that handles thousands of requests per second. When you deploy it to production, how does it know to shut down gracefully when the server needs maintenance? How does your application read database credentials without hardcoding them? How does it start child processes for background tasks?

Operating system integration is the foundation that separates toy programs from production-ready applications. It's how your Go code becomes a good citizen of the operating system - handling signals, managing processes, reading configuration, and interacting with the OS like a professional.

In modern cloud environments and DevOps practices, OS integration skills are essential for building:

  • Graceful services that respond to shutdown signals
  • Process orchestrators that manage multiple child processes
  • Configuration systems that adapt to different environments
  • Monitoring tools that understand system resources
  • Background workers that run long-running tasks

Learning Objectives

After completing this article, you will be able to:

  1. Handle System Signals: Implement graceful shutdown for applications
  2. Manage Processes: Create and control child processes from Go programs
  3. Work with Environment: Load and validate configuration from environment variables
  4. Integrate with OS: Use file system operations, user management, and system resources
  5. Build Production Patterns: Implement daemon processes, health checks, and monitoring
  6. Handle Cross-Platform: Write code that works across Windows, Linux, and macOS
  7. Implement Best Practices: Use timeouts, error handling, and resource management

Core Concepts

The OS-Program Relationship

Your Go program doesn't run in isolation—it's constantly interacting with the operating system in a structured conversation:

  1. Program Initialization: OS loads your executable, allocates memory, sets up environment
  2. Resource Requests: Your program asks for files, network sockets, memory, or other resources
  3. System Communication: OS sends signals and notifications about system events
  4. Process Lifecycle: Your program starts, runs, potentially spawns children, then exits
  5. Cleanup: OS reclaims resources when your program terminates

Think of this relationship like a client-server protocol where your program is the client and the OS is the server providing essential services.

Go's OS Architecture

Go provides multiple layers of OS interaction:

  • os package: High-level, portable OS operations
  • syscall package: Low-level, platform-specific system calls
  • os/exec package: Process management and execution
  • os/signal package: Signal handling and notification
  • os/user package: User and group information

This layered approach lets you choose the right level of abstraction for your needs.

Understanding System Signals

Signals are the OS's way of sending urgent messages to your program. Common signals include:

  • SIGINT (Interrupt): Sent when user presses Ctrl+C
  • SIGTERM (Terminate): Polite shutdown request from OS or process manager
  • SIGKILL (Kill): Immediate termination (cannot be caught)
  • SIGHUP (Hangup): Terminal disconnected, often used to reload configuration
  • SIGQUIT (Quit): Quit with core dump for debugging

Understanding and handling these signals properly is essential for production applications.

Practical Examples

Environment Variables and Configuration

Environment variables are how the OS and deployment systems communicate configuration to your application. They're essential for:

  • Database credentials in production
  • Feature flags and settings
  • Deployment environment information
  • Service discovery and configuration
  1package main
  2
  3import (
  4    "fmt"
  5    "os"
  6    "strconv"
  7    "strings"
  8)
  9
 10// run
 11func main() {
 12    // 1. Reading environment variables
 13    fmt.Println("=== Environment Variables ===")
 14
 15    // Get specific variable with fallback
 16    port := getEnvWithDefault("PORT", "8080")
 17    fmt.Printf("Server port: %s\n", port)
 18
 19    // Get boolean flag
 20    debug := getEnvBool("DEBUG", false)
 21    fmt.Printf("Debug mode: %t\n", debug)
 22
 23    // Get comma-separated list
 24    allowedHosts := getEnvSlice("ALLOWED_HOSTS", []string{"localhost"})
 25    fmt.Printf("Allowed hosts: %v\n", allowedHosts)
 26
 27    // 2. Setting environment variables
 28    fmt.Println("\n=== Setting Environment ===")
 29
 30    // Set for current process only
 31    os.Setenv("APP_VERSION", "1.0.0")
 32    fmt.Printf("App version: %s\n", os.Getenv("APP_VERSION"))
 33
 34    // 3. Environment validation
 35    fmt.Println("\n=== Configuration Validation ===")
 36
 37    config, err := loadAndValidateConfig()
 38    if err != nil {
 39        fmt.Printf("Configuration error: %v\n", err)
 40        os.Exit(1)
 41    }
 42
 43    fmt.Printf("Valid configuration: %+v\n", config)
 44}
 45
 46// Helper: Get environment variable with default value
 47func getEnvWithDefault(key, defaultValue string) string {
 48    if value := os.Getenv(key); value != "" {
 49        return value
 50    }
 51    return defaultValue
 52}
 53
 54// Helper: Get boolean environment variable
 55func getEnvBool(key string, defaultValue bool) bool {
 56    if value := strings.ToLower(os.Getenv(key)); value != "" {
 57        return value == "true" || value == "1" || value == "yes"
 58    }
 59    return defaultValue
 60}
 61
 62// Helper: Get slice from comma-separated environment variable
 63func getEnvSlice(key string, defaultValue []string) []string {
 64    if value := os.Getenv(key); value != "" {
 65        return strings.Split(value, ",")
 66    }
 67    return defaultValue
 68}
 69
 70// Configuration structure
 71type Config struct {
 72    Port         int
 73    Debug        bool
 74    DatabaseURL  string
 75    AllowedHosts []string
 76}
 77
 78// Load and validate configuration from environment
 79func loadAndValidateConfig() (*Config, error) {
 80    config := &Config{
 81        Port:         getEnvInt("PORT", 8080),
 82        Debug:        getEnvBool("DEBUG", false),
 83        DatabaseURL:  getEnvWithDefault("DATABASE_URL", "postgres://localhost:5432/db"),
 84        AllowedHosts: getEnvSlice("ALLOWED_HOSTS", []string{"localhost"}),
 85    }
 86
 87    // Validate configuration
 88    if config.Port < 1 || config.Port > 65535 {
 89        return nil, fmt.Errorf("invalid port: %d", config.Port)
 90    }
 91
 92    if config.DatabaseURL == "" {
 93        return nil, fmt.Errorf("database URL is required")
 94    }
 95
 96    if len(config.AllowedHosts) == 0 {
 97        return nil, fmt.Errorf("at least one allowed host is required")
 98    }
 99
100    return config, nil
101}
102
103// Helper: Get integer environment variable
104func getEnvInt(key string, defaultValue int) int {
105    if value := os.Getenv(key); value != "" {
106        if intValue, err := strconv.Atoi(value); err == nil {
107            return intValue
108        }
109    }
110    return defaultValue
111}

Real-World Applications:

  • 12-Factor Apps: Environment-based configuration
  • Container Orchestration: Kubernetes and Docker pass configuration via environment
  • CI/CD Pipelines: Different environments use different variable values
  • Security: Separating configuration from codebase

💡 Common Pitfall: Don't store sensitive data in environment variables in production logs. Use secure secret management systems instead.

Signal Handling for Graceful Shutdown

Signals are the OS's way of sending urgent messages to your program. Proper signal handling is what separates robust applications from fragile ones.

 1package main
 2
 3import (
 4    "context"
 5    "fmt"
 6    "log"
 7    "net/http"
 8    "os"
 9    "os/signal"
10    "syscall"
11    "time"
12)
13
14// run
15func main() {
16    // Create context for graceful shutdown
17    ctx, cancel := context.WithCancel(context.Background())
18
19    // Set up signal handling
20    setupSignalHandling(cancel)
21
22    // Start HTTP server
23    server := &http.Server{
24        Addr:    ":8080",
25        Handler: http.HandlerFunc(handler),
26    }
27
28    // Start server in goroutine
29    go func() {
30        log.Println("Server starting on :8080")
31        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
32            log.Printf("Server error: %v", err)
33        }
34    }()
35
36    // Wait for shutdown signal
37    <-ctx.Done()
38    log.Println("Shutting down server...")
39
40    // Graceful shutdown with timeout
41    shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
42    defer shutdownCancel()
43
44    if err := server.Shutdown(shutdownCtx); err != nil {
45        log.Printf("Server shutdown error: %v", err)
46    }
47
48    log.Println("Server stopped")
49}
50
51func setupSignalHandling(cancel context.CancelFunc) {
52    sigChan := make(chan os.Signal, 1)
53
54    // Register for common shutdown signals
55    signal.Notify(sigChan,
56        syscall.SIGINT,  // Ctrl+C
57        syscall.SIGTERM, // Termination signal
58        syscall.SIGQUIT, // Quit with core dump
59    )
60
61    go func() {
62        sig := <-sigChan
63        log.Printf("Received signal: %v", sig)
64
65        switch sig {
66        case syscall.SIGINT:
67            log.Println("Interrupt signal received")
68        case syscall.SIGTERM:
69            log.Println("Termination signal received")
70        case syscall.SIGQUIT:
71            log.Println("Quit signal received")
72        }
73
74        // Trigger graceful shutdown
75        cancel()
76    }()
77}
78
79func handler(w http.ResponseWriter, r *http.Request) {
80    select {
81    case <-time.After(2 * time.Second):
82        // Simulate work
83        fmt.Fprintf(w, "Hello, World! Request processed in 2 seconds")
84    case <-r.Context().Done():
85        // Request cancelled due to shutdown
86        log.Printf("Request cancelled: %s", r.URL.Path)
87        return
88    }
89}

Real-World Applications:

  • Web Servers: Close connections gracefully before shutdown
  • Database Connections: Commit transactions and close connections
  • Background Workers: Finish current jobs before terminating
  • File Operations: Complete writes and close file handles

💡 Best Practice: Always implement a timeout for graceful shutdown. If the application doesn't shut down within the timeout, force exit to prevent hanging.

Process Management and Execution

Process management lets your Go application spawn and control other programs, essential for building tools, orchestrators, and automation systems.

  1package main
  2
  3import (
  4    "bufio"
  5    "context"
  6    "fmt"
  7    "io"
  8    "os"
  9    "os/exec"
 10    "sync"
 11    "time"
 12)
 13
 14// run
 15func main() {
 16    fmt.Println("=== Process Management Examples ===")
 17
 18    // 1. Simple command execution
 19    executeSimpleCommand()
 20
 21    // 2. Command with arguments
 22    executeCommandWithArgs()
 23
 24    // 3. Capture output
 25    captureCommandOutput()
 26
 27    // 4. Stream output in real-time
 28    streamCommandOutput()
 29
 30    // 5. Process management with timeout
 31    executeWithTimeout()
 32
 33    // 6. Process pool for parallel execution
 34    executeWithProcessPool()
 35}
 36
 37func executeSimpleCommand() {
 38    fmt.Println("\n1. Simple Command Execution")
 39
 40    cmd := exec.Command("echo", "Hello from child process")
 41
 42    output, err := cmd.CombinedOutput()
 43    if err != nil {
 44        fmt.Printf("Error: %v\n", err)
 45        return
 46    }
 47
 48    fmt.Printf("Output: %s", string(output))
 49}
 50
 51func executeCommandWithArgs() {
 52    fmt.Println("\n2. Command with Arguments")
 53
 54    // Safer way to handle command arguments
 55    args := []string{"-c", "echo 'Arguments passed safely'"}
 56    cmd := exec.Command("sh", args...)
 57
 58    output, err := cmd.CombinedOutput()
 59    if err != nil {
 60        fmt.Printf("Error: %v\n", err)
 61        return
 62    }
 63
 64    fmt.Printf("Output: %s", string(output))
 65}
 66
 67func captureCommandOutput() {
 68    fmt.Println("\n3. Capturing Command Output")
 69
 70    cmd := exec.Command("ls", "-la")
 71
 72    stdout, err := cmd.StdoutPipe()
 73    if err != nil {
 74        fmt.Printf("Error creating stdout pipe: %v\n", err)
 75        return
 76    }
 77
 78    stderr, err := cmd.StderrPipe()
 79    if err != nil {
 80        fmt.Printf("Error creating stderr pipe: %v\n", err)
 81        return
 82    }
 83
 84    if err := cmd.Start(); err != nil {
 85        fmt.Printf("Error starting command: %v\n", err)
 86        return
 87    }
 88
 89    // Read stdout and stderr concurrently
 90    var wg sync.WaitGroup
 91    wg.Add(2)
 92
 93    go func() {
 94        defer wg.Done()
 95        scanner := bufio.NewScanner(stdout)
 96        for scanner.Scan() {
 97            fmt.Printf("STDOUT: %s\n", scanner.Text())
 98        }
 99    }()
100
101    go func() {
102        defer wg.Done()
103        scanner := bufio.NewScanner(stderr)
104        for scanner.Scan() {
105            fmt.Printf("STDERR: %s\n", scanner.Text())
106        }
107    }()
108
109    wg.Wait()
110    cmd.Wait()
111}
112
113func streamCommandOutput() {
114    fmt.Println("\n4. Real-time Output Streaming")
115
116    cmd := exec.Command("ping", "-c", "3", "localhost")
117
118    stdout, err := cmd.StdoutPipe()
119    if err != nil {
120        fmt.Printf("Error: %v\n", err)
121        return
122    }
123
124    if err := cmd.Start(); err != nil {
125        fmt.Printf("Error: %v\n", err)
126        return
127    }
128
129    // Stream output in real-time
130    scanner := bufio.NewScanner(stdout)
131    for scanner.Scan() {
132        fmt.Printf("Live output: %s\n", scanner.Text())
133    }
134
135    cmd.Wait()
136}
137
138func executeWithTimeout() {
139    fmt.Println("\n5. Command with Timeout")
140
141    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
142    defer cancel()
143
144    cmd := exec.CommandContext(ctx, "sleep", "5")
145
146    err := cmd.Run()
147    if err != nil {
148        fmt.Printf("Command failed: %v\n", err)
149    } else {
150        fmt.Println("Command completed successfully")
151    }
152}
153
154func executeWithProcessPool() {
155    fmt.Println("\n6. Process Pool for Parallel Execution")
156
157    commands := []string{
158        "echo 'Task 1 complete'",
159        "echo 'Task 2 complete'",
160        "echo 'Task 3 complete'",
161        "echo 'Task 4 complete'",
162    }
163
164    var wg sync.WaitGroup
165    semaphore := make(chan struct{}, 2) // Limit to 2 concurrent processes
166
167    for i, cmdStr := range commands {
168        wg.Add(1)
169        go func(id int, command string) {
170            defer wg.Done()
171
172            semaphore <- struct{}{}        // Acquire
173            defer func() { <-semaphore }() // Release
174
175            cmd := exec.Command("sh", "-c", command)
176            output, err := cmd.CombinedOutput()
177
178            if err != nil {
179                fmt.Printf("Task %d failed: %v\n", id, err)
180            } else {
181                fmt.Printf("Task %d: %s\n", id, string(output))
182            }
183        }(i, cmdStr)
184    }
185
186    wg.Wait()
187    fmt.Println("All tasks completed")
188}

Real-World Applications:

  • Build Systems: Compile multiple files in parallel
  • CI/CD Pipelines: Execute build, test, and deployment steps
  • Data Processing: Run external tools for data transformation
  • System Administration: Automate system maintenance tasks

⚠️ Security Warning: Never pass unsanitized user input to exec.Command. Use proper argument arrays to prevent command injection.

Working with File System and Paths

Operating systems have different conventions for file paths, permissions, and file operations. Go's os package provides portable abstractions.

  1package main
  2
  3import (
  4    "fmt"
  5    "io/fs"
  6    "os"
  7    "path/filepath"
  8    "time"
  9)
 10
 11// run
 12func main() {
 13    fmt.Println("=== File System Operations ===")
 14
 15    // 1. Working directory operations
 16    fmt.Println("\n1. Working Directory:")
 17    workingDir()
 18
 19    // 2. File information and metadata
 20    fmt.Println("\n2. File Information:")
 21    fileInformation()
 22
 23    // 3. Directory traversal
 24    fmt.Println("\n3. Directory Traversal:")
 25    directoryTraversal()
 26
 27    // 4. File permissions
 28    fmt.Println("\n4. File Permissions:")
 29    filePermissions()
 30
 31    // 5. Temporary files and directories
 32    fmt.Println("\n5. Temporary Files:")
 33    temporaryFiles()
 34}
 35
 36func workingDir() {
 37    // Get current working directory
 38    cwd, err := os.Getwd()
 39    if err != nil {
 40        fmt.Printf("Error getting working directory: %v\n", err)
 41        return
 42    }
 43    fmt.Printf("Current directory: %s\n", cwd)
 44
 45    // Get home directory
 46    home, err := os.UserHomeDir()
 47    if err != nil {
 48        fmt.Printf("Error getting home directory: %v\n", err)
 49        return
 50    }
 51    fmt.Printf("Home directory: %s\n", home)
 52
 53    // Get executable path
 54    executable, err := os.Executable()
 55    if err != nil {
 56        fmt.Printf("Error getting executable path: %v\n", err)
 57        return
 58    }
 59    fmt.Printf("Executable path: %s\n", executable)
 60}
 61
 62func fileInformation() {
 63    // Create a temporary file for demonstration
 64    tmpFile, err := os.CreateTemp("", "example-*.txt")
 65    if err != nil {
 66        fmt.Printf("Error creating temp file: %v\n", err)
 67        return
 68    }
 69    defer os.Remove(tmpFile.Name())
 70    defer tmpFile.Close()
 71
 72    // Write some data
 73    tmpFile.WriteString("Hello, World!")
 74
 75    // Get file information
 76    fileInfo, err := os.Stat(tmpFile.Name())
 77    if err != nil {
 78        fmt.Printf("Error getting file info: %v\n", err)
 79        return
 80    }
 81
 82    fmt.Printf("File name: %s\n", fileInfo.Name())
 83    fmt.Printf("Size: %d bytes\n", fileInfo.Size())
 84    fmt.Printf("Permissions: %s\n", fileInfo.Mode())
 85    fmt.Printf("Last modified: %s\n", fileInfo.ModTime().Format(time.RFC3339))
 86    fmt.Printf("Is directory: %t\n", fileInfo.IsDir())
 87}
 88
 89func directoryTraversal() {
 90    // Get temporary directory
 91    tmpDir := os.TempDir()
 92
 93    // Walk directory tree
 94    count := 0
 95    err := filepath.WalkDir(tmpDir, func(path string, d fs.DirEntry, err error) error {
 96        if err != nil {
 97            return err
 98        }
 99
100        count++
101        if count > 5 {
102            return filepath.SkipDir // Stop after 5 entries
103        }
104
105        info, _ := d.Info()
106        fmt.Printf("Path: %s (size: %d bytes)\n", path, info.Size())
107        return nil
108    })
109
110    if err != nil {
111        fmt.Printf("Error walking directory: %v\n", err)
112    }
113}
114
115func filePermissions() {
116    // Create a file with specific permissions
117    tmpFile, err := os.CreateTemp("", "permissions-*.txt")
118    if err != nil {
119        fmt.Printf("Error creating temp file: %v\n", err)
120        return
121    }
122    defer os.Remove(tmpFile.Name())
123    tmpFile.Close()
124
125    // Set read-only permissions
126    err = os.Chmod(tmpFile.Name(), 0444)
127    if err != nil {
128        fmt.Printf("Error changing permissions: %v\n", err)
129        return
130    }
131
132    fileInfo, _ := os.Stat(tmpFile.Name())
133    fmt.Printf("File permissions: %s\n", fileInfo.Mode())
134
135    // Check if file is readable, writable, executable
136    mode := fileInfo.Mode()
137    fmt.Printf("Readable: %t\n", mode&0400 != 0)
138    fmt.Printf("Writable: %t\n", mode&0200 != 0)
139    fmt.Printf("Executable: %t\n", mode&0100 != 0)
140}
141
142func temporaryFiles() {
143    // Create temporary file
144    tmpFile, err := os.CreateTemp("", "temp-*.txt")
145    if err != nil {
146        fmt.Printf("Error: %v\n", err)
147        return
148    }
149    defer os.Remove(tmpFile.Name())
150
151    fmt.Printf("Temporary file created: %s\n", tmpFile.Name())
152
153    // Create temporary directory
154    tmpDir, err := os.MkdirTemp("", "temp-dir-*")
155    if err != nil {
156        fmt.Printf("Error: %v\n", err)
157        return
158    }
159    defer os.RemoveAll(tmpDir)
160
161    fmt.Printf("Temporary directory created: %s\n", tmpDir)
162}

User and Group Management

Understanding users and groups is essential for security, permissions, and multi-tenant applications.

 1package main
 2
 3import (
 4    "fmt"
 5    "os"
 6    "os/user"
 7)
 8
 9// run
10func main() {
11    fmt.Println("=== User and Group Management ===")
12
13    // 1. Current user information
14    fmt.Println("\n1. Current User:")
15    currentUser()
16
17    // 2. User lookup
18    fmt.Println("\n2. User Lookup:")
19    userLookup()
20
21    // 3. Process credentials
22    fmt.Println("\n3. Process Credentials:")
23    processCredentials()
24}
25
26func currentUser() {
27    current, err := user.Current()
28    if err != nil {
29        fmt.Printf("Error getting current user: %v\n", err)
30        return
31    }
32
33    fmt.Printf("Username: %s\n", current.Username)
34    fmt.Printf("UID: %s\n", current.Uid)
35    fmt.Printf("GID: %s\n", current.Gid)
36    fmt.Printf("Home directory: %s\n", current.HomeDir)
37}
38
39func userLookup() {
40    // Lookup current user by UID
41    current, _ := user.Current()
42
43    usr, err := user.LookupId(current.Uid)
44    if err != nil {
45        fmt.Printf("Error looking up user: %v\n", err)
46        return
47    }
48
49    fmt.Printf("User found: %s (UID: %s)\n", usr.Username, usr.Uid)
50
51    // Get user's groups
52    groups, err := usr.GroupIds()
53    if err != nil {
54        fmt.Printf("Error getting groups: %v\n", err)
55        return
56    }
57
58    fmt.Printf("User belongs to %d groups\n", len(groups))
59}
60
61func processCredentials() {
62    // Get process UID and GID
63    uid := os.Getuid()
64    gid := os.Getgid()
65
66    fmt.Printf("Process UID: %d\n", uid)
67    fmt.Printf("Process GID: %d\n", gid)
68    fmt.Printf("Process is privileged: %t\n", uid == 0)
69}

Common Patterns and Pitfalls

Production Best Practices

 1package main
 2
 3import (
 4    "context"
 5    "fmt"
 6    "log"
 7    "os"
 8    "os/signal"
 9    "syscall"
10    "time"
11)
12
13// run
14func main() {
15    app := &Application{
16        name:    "ProductionService",
17        version: "1.0.0",
18    }
19
20    if err := app.Run(); err != nil {
21        log.Fatalf("Application failed: %v", err)
22    }
23}
24
25type Application struct {
26    name    string
27    version string
28}
29
30func (app *Application) Run() error {
31    // 1. Setup graceful shutdown
32    ctx, cancel := app.setupGracefulShutdown()
33    defer cancel()
34
35    // 2. Initialize resources
36    if err := app.initialize(ctx); err != nil {
37        return fmt.Errorf("initialization failed: %w", err)
38    }
39    defer app.cleanup()
40
41    // 3. Start main service
42    return app.startService(ctx)
43}
44
45func (app *Application) setupGracefulShutdown() (context.Context, context.CancelFunc) {
46    ctx, cancel := context.WithCancel(context.Background())
47
48    sigChan := make(chan os.Signal, 1)
49    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
50
51    go func() {
52        sig := <-sigChan
53        log.Printf("Received signal %v, initiating graceful shutdown", sig)
54        cancel()
55    }()
56
57    return ctx, cancel
58}
59
60func (app *Application) initialize(ctx context.Context) error {
61    log.Printf("Initializing %s v%s", app.name, app.version)
62
63    // Simulate initialization
64    select {
65    case <-time.After(2 * time.Second):
66        log.Println("Initialization complete")
67        return nil
68    case <-ctx.Done():
69        return ctx.Err()
70    }
71}
72
73func (app *Application) startService(ctx context.Context) error {
74    log.Println("Starting service...")
75
76    ticker := time.NewTicker(1 * time.Second)
77    defer ticker.Stop()
78
79    for {
80        select {
81        case <-ticker.C:
82            log.Println("Service running...")
83
84        case <-ctx.Done():
85            log.Println("Service shutting down...")
86            return nil
87        }
88    }
89}
90
91func (app *Application) cleanup() {
92    log.Println("Cleaning up resources...")
93    // Close database connections, file handles, etc.
94}

Cross-Platform Considerations

  1package main
  2
  3import (
  4    "fmt"
  5    "os"
  6    "os/exec"
  7    "runtime"
  8    "strings"
  9)
 10
 11// run
 12func main() {
 13    fmt.Printf("Running on %s/%s\n", runtime.GOOS, runtime.GOARCH)
 14
 15    // 1. Platform-specific commands
 16    if err := runPlatformSpecificCommand(); err != nil {
 17        fmt.Printf("Command failed: %v\n", err)
 18    }
 19
 20    // 2. Platform-specific paths
 21    showPlatformPaths()
 22
 23    // 3. Platform-specific features
 24    demonstratePlatformFeatures()
 25}
 26
 27func runPlatformSpecificCommand() error {
 28    var cmd *exec.Cmd
 29
 30    switch runtime.GOOS {
 31    case "windows":
 32        cmd = exec.Command("cmd", "/c", "dir")
 33    case "linux", "darwin":
 34        cmd = exec.Command("ls", "-la")
 35    default:
 36        return fmt.Errorf("unsupported platform: %s", runtime.GOOS)
 37    }
 38
 39    output, err := cmd.CombinedOutput()
 40    if err != nil {
 41        return err
 42    }
 43
 44    fmt.Printf("Command output:\n%s\n", string(output))
 45    return nil
 46}
 47
 48func showPlatformPaths() {
 49    // Home directory
 50    if home, err := os.UserHomeDir(); err == nil {
 51        fmt.Printf("Home directory: %s\n", home)
 52    }
 53
 54    // Config directory
 55    var configDir string
 56    switch runtime.GOOS {
 57    case "windows":
 58        configDir = os.Getenv("APPDATA")
 59    case "darwin":
 60        configDir = fmt.Sprintf("%s/Library/Application Support", os.Getenv("HOME"))
 61    default: // Linux and others
 62        configDir = fmt.Sprintf("%s/.config", os.Getenv("HOME"))
 63    }
 64    fmt.Printf("Config directory: %s\n", configDir)
 65}
 66
 67func demonstratePlatformFeatures() {
 68    // Check if running in container
 69    if isRunningInContainer() {
 70        fmt.Println("Running in container environment")
 71    }
 72
 73    // Check if running with elevated privileges
 74    if isRunningElevated() {
 75        fmt.Println("Running with elevated privileges")
 76    }
 77}
 78
 79func isRunningInContainer() bool {
 80    // Check for Docker container indicators
 81    if _, err := os.Stat("/.dockerenv"); err == nil {
 82        return true
 83    }
 84
 85    // Check cgroup information
 86    if runtime.GOOS == "linux" {
 87        if data, err := os.ReadFile("/proc/1/cgroup"); err == nil {
 88            return strings.Contains(string(data), "docker") ||
 89                strings.Contains(string(data), "containerd")
 90        }
 91    }
 92
 93    return false
 94}
 95
 96func isRunningElevated() bool {
 97    switch runtime.GOOS {
 98    case "windows":
 99        // Windows: check if running as Administrator
100        return os.Getenv("USERNAME") == "Administrator"
101    default:
102        // Unix-like: check if running as root
103        return os.Getuid() == 0
104    }
105}

Advanced File System Operations

  1package main
  2
  3import (
  4    "fmt"
  5    "io"
  6    "os"
  7    "path/filepath"
  8)
  9
 10// run
 11func main() {
 12    fmt.Println("=== Advanced File System Operations ===")
 13
 14    // 1. Atomic file operations
 15    fmt.Println("\n1. Atomic File Operations:")
 16    atomicFileWrite()
 17
 18    // 2. File locking
 19    fmt.Println("\n2. File Locking:")
 20    fileLocking()
 21
 22    // 3. Symlink operations
 23    fmt.Println("\n3. Symlink Operations:")
 24    symlinkOperations()
 25}
 26
 27func atomicFileWrite() {
 28    target := filepath.Join(os.TempDir(), "atomic-file.txt")
 29
 30    // Create temporary file in same directory
 31    tmpFile, err := os.CreateTemp(filepath.Dir(target), ".tmp-*")
 32    if err != nil {
 33        fmt.Printf("Error creating temp file: %v\n", err)
 34        return
 35    }
 36    tmpName := tmpFile.Name()
 37
 38    // Write to temporary file
 39    _, err = tmpFile.WriteString("Atomic write content")
 40    tmpFile.Close()
 41
 42    if err != nil {
 43        os.Remove(tmpName)
 44        fmt.Printf("Error writing: %v\n", err)
 45        return
 46    }
 47
 48    // Atomic rename
 49    err = os.Rename(tmpName, target)
 50    if err != nil {
 51        os.Remove(tmpName)
 52        fmt.Printf("Error renaming: %v\n", err)
 53        return
 54    }
 55
 56    fmt.Printf("Atomic write successful: %s\n", target)
 57    os.Remove(target)
 58}
 59
 60func fileLocking() {
 61    // File locking is platform-specific
 62    // On Unix-like systems, use flock
 63    // On Windows, use LockFileEx
 64
 65    fmt.Println("File locking is platform-specific")
 66    fmt.Println("Use sync.Mutex for in-process synchronization")
 67    fmt.Println("Use external tools (flock, lockfile) for inter-process locking")
 68}
 69
 70func symlinkOperations() {
 71    target := filepath.Join(os.TempDir(), "symlink-target.txt")
 72    link := filepath.Join(os.TempDir(), "symlink-link.txt")
 73
 74    // Create target file
 75    err := os.WriteFile(target, []byte("Target content"), 0644)
 76    if err != nil {
 77        fmt.Printf("Error creating target: %v\n", err)
 78        return
 79    }
 80    defer os.Remove(target)
 81
 82    // Create symlink
 83    err = os.Symlink(target, link)
 84    if err != nil {
 85        fmt.Printf("Error creating symlink: %v\n", err)
 86        return
 87    }
 88    defer os.Remove(link)
 89
 90    // Read through symlink
 91    content, err := os.ReadFile(link)
 92    if err != nil {
 93        fmt.Printf("Error reading symlink: %v\n", err)
 94        return
 95    }
 96
 97    fmt.Printf("Content through symlink: %s\n", string(content))
 98
 99    // Get symlink target
100    linkTarget, err := os.Readlink(link)
101    if err != nil {
102        fmt.Printf("Error reading link target: %v\n", err)
103        return
104    }
105
106    fmt.Printf("Symlink points to: %s\n", linkTarget)
107}

Integration and Mastery

Advanced Process Orchestration

 1package main
 2
 3import (
 4    "context"
 5    "fmt"
 6    "sync"
 7    "time"
 8)
 9
10// ProcessPool manages multiple concurrent processes
11type ProcessPool struct {
12    maxWorkers int
13    taskQueue  chan Task
14    workers    []*Worker
15    wg         sync.WaitGroup
16}
17
18type Task struct {
19    ID      int
20    Command string
21    Args    []string
22    Timeout time.Duration
23}
24
25type Worker struct {
26    ID       int
27    TaskChan chan Task
28    Done     chan bool
29}
30
31// run
32func main() {
33    pool := NewProcessPool(3)
34
35    // Add tasks to pool
36    tasks := []Task{
37        {ID: 1, Command: "echo", Args: []string{"Task 1"}, Timeout: 2 * time.Second},
38        {ID: 2, Command: "sleep", Args: []string{"1"}, Timeout: 5 * time.Second},
39        {ID: 3, Command: "echo", Args: []string{"Task 3"}, Timeout: 2 * time.Second},
40        {ID: 4, Command: "sleep", Args: []string{"2"}, Timeout: 5 * time.Second},
41    }
42
43    for _, task := range tasks {
44        pool.Submit(task)
45    }
46
47    // Wait for all tasks to complete
48    pool.Wait()
49    fmt.Println("All tasks completed")
50}
51
52func NewProcessPool(maxWorkers int) *ProcessPool {
53    pool := &ProcessPool{
54        maxWorkers: maxWorkers,
55        taskQueue:  make(chan Task, 10),
56        workers:    make([]*Worker, maxWorkers),
57    }
58
59    // Start workers
60    for i := 0; i < maxWorkers; i++ {
61        worker := &Worker{
62            ID:       i,
63            TaskChan: make(chan Task),
64            Done:     make(chan bool),
65        }
66        pool.workers[i] = worker
67        pool.wg.Add(1)
68        go worker.Start(&pool.wg, pool.taskQueue)
69    }
70
71    return pool
72}
73
74func (p *ProcessPool) Submit(task Task) {
75    p.taskQueue <- task
76}
77
78func (p *ProcessPool) Wait() {
79    close(p.taskQueue)
80    p.wg.Wait()
81}
82
83func (w *Worker) Start(wg *sync.WaitGroup, taskChan <-chan Task) {
84    defer wg.Done()
85
86    for task := range taskChan {
87        w.executeTask(task)
88    }
89}
90
91func (w *Worker) executeTask(task Task) {
92    fmt.Printf("Worker %d executing task %d: %s %v\n",
93        w.ID, task.ID, task.Command, task.Args)
94
95    // Simulate task execution
96    time.Sleep(500 * time.Millisecond)
97
98    fmt.Printf("Worker %d completed task %d\n", w.ID, task.ID)
99}

Building a Daemon Service

  1package main
  2
  3import (
  4    "context"
  5    "fmt"
  6    "log"
  7    "os"
  8    "os/signal"
  9    "syscall"
 10    "time"
 11)
 12
 13// run
 14func main() {
 15    daemon := &Daemon{
 16        name:     "example-daemon",
 17        pidFile:  "/tmp/example-daemon.pid",
 18        logFile:  "/tmp/example-daemon.log",
 19        interval: 5 * time.Second,
 20    }
 21
 22    if err := daemon.Start(); err != nil {
 23        log.Fatalf("Daemon failed: %v", err)
 24    }
 25}
 26
 27type Daemon struct {
 28    name     string
 29    pidFile  string
 30    logFile  string
 31    interval time.Duration
 32}
 33
 34func (d *Daemon) Start() error {
 35    // Write PID file
 36    if err := d.writePIDFile(); err != nil {
 37        return fmt.Errorf("failed to write PID file: %w", err)
 38    }
 39    defer d.removePIDFile()
 40
 41    // Setup logging
 42    if err := d.setupLogging(); err != nil {
 43        return fmt.Errorf("failed to setup logging: %w", err)
 44    }
 45
 46    log.Printf("Starting daemon: %s", d.name)
 47
 48    // Setup signal handling
 49    ctx, cancel := d.setupSignalHandling()
 50    defer cancel()
 51
 52    // Run main loop
 53    return d.run(ctx)
 54}
 55
 56func (d *Daemon) writePIDFile() error {
 57    pid := os.Getpid()
 58    return os.WriteFile(d.pidFile, []byte(fmt.Sprintf("%d\n", pid)), 0644)
 59}
 60
 61func (d *Daemon) removePIDFile() {
 62    os.Remove(d.pidFile)
 63}
 64
 65func (d *Daemon) setupLogging() error {
 66    logFile, err := os.OpenFile(d.logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
 67    if err != nil {
 68        return err
 69    }
 70
 71    log.SetOutput(logFile)
 72    return nil
 73}
 74
 75func (d *Daemon) setupSignalHandling() (context.Context, context.CancelFunc) {
 76    ctx, cancel := context.WithCancel(context.Background())
 77
 78    sigChan := make(chan os.Signal, 1)
 79    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
 80
 81    go func() {
 82        for sig := range sigChan {
 83            switch sig {
 84            case syscall.SIGHUP:
 85                log.Println("Received SIGHUP, reloading configuration")
 86                d.reload()
 87            case syscall.SIGINT, syscall.SIGTERM:
 88                log.Printf("Received %v, shutting down", sig)
 89                cancel()
 90                return
 91            }
 92        }
 93    }()
 94
 95    return ctx, cancel
 96}
 97
 98func (d *Daemon) run(ctx context.Context) error {
 99    ticker := time.NewTicker(d.interval)
100    defer ticker.Stop()
101
102    for {
103        select {
104        case <-ticker.C:
105            d.doWork()
106        case <-ctx.Done():
107            log.Println("Daemon shutting down")
108            return nil
109        }
110    }
111}
112
113func (d *Daemon) doWork() {
114    log.Printf("Daemon %s performing periodic work", d.name)
115    // Perform actual daemon work here
116}
117
118func (d *Daemon) reload() {
119    log.Println("Reloading daemon configuration")
120    // Reload configuration here
121}

Practice Exercises

Exercise 1: Process Manager with Signal Handling

Learning Objectives: Master process lifecycle management, signal handling, and graceful shutdown patterns.

Real-World Context: Process managers are essential for building robust services, job schedulers, and orchestration systems. Tools like Docker, Kubernetes, and systemd all rely on sophisticated process management. This exercise teaches you to build the core patterns used in production systems.

Difficulty: Intermediate | Time Estimate: 2-3 hours

Description: Create a comprehensive process manager that can start, stop, monitor, and gracefully manage multiple child processes with proper signal handling and resource cleanup.

Requirements:

  • Process pool with configurable worker limits
  • Graceful shutdown with signal propagation to child processes
  • Process health monitoring and automatic restart
  • Resource usage tracking
  • Process output capture and logging
  • Timeout handling for stuck processes
  • Process dependency management
  • Configuration via environment variables
  • Cross-platform compatibility
  • Integration testing with mock processes
Solution
  1package main
  2
  3import (
  4    "context"
  5    "fmt"
  6    "io"
  7    "log"
  8    "os"
  9    "os/exec"
 10    "os/signal"
 11    "sync"
 12    "syscall"
 13    "time"
 14)
 15
 16// ProcessManager manages multiple processes
 17type ProcessManager struct {
 18    maxProcesses int
 19    processes    map[int]*ManagedProcess
 20    nextID       int
 21    mutex        sync.RWMutex
 22    ctx          context.Context
 23    cancel       context.CancelFunc
 24    wg           sync.WaitGroup
 25}
 26
 27// ManagedProcess wraps os.Process with additional metadata
 28type ManagedProcess struct {
 29    ID           int
 30    Cmd          *exec.Cmd
 31    Process      *os.Process
 32    Status       ProcessStatus
 33    StartTime    time.Time
 34    RestartCount int
 35    Output       chan string
 36    Error        chan error
 37    mutex        sync.RWMutex
 38}
 39
 40type ProcessStatus int
 41
 42const (
 43    StatusStopped ProcessStatus = iota
 44    StatusRunning
 45    StatusStopping
 46    StatusRestarting
 47    StatusFailed
 48)
 49
 50// NewProcessManager creates a new process manager
 51func NewProcessManager(maxProcesses int) *ProcessManager {
 52    ctx, cancel := context.WithCancel(context.Background())
 53
 54    pm := &ProcessManager{
 55        maxProcesses: maxProcesses,
 56        processes:    make(map[int]*ManagedProcess),
 57        ctx:          ctx,
 58        cancel:       cancel,
 59    }
 60
 61    // Setup signal handling
 62    pm.setupSignalHandling()
 63
 64    return pm
 65}
 66
 67func (pm *ProcessManager) setupSignalHandling() {
 68    sigChan := make(chan os.Signal, 1)
 69    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
 70
 71    go func() {
 72        sig := <-sigChan
 73        log.Printf("Received signal %v, shutting down all processes", sig)
 74        pm.Shutdown()
 75    }()
 76}
 77
 78// StartProcess starts a new process
 79func (pm *ProcessManager) StartProcess(name string, args []string, env []string) (int, error) {
 80    pm.mutex.Lock()
 81    defer pm.mutex.Unlock()
 82
 83    // Check process limit
 84    runningCount := 0
 85    for _, proc := range pm.processes {
 86        if proc.Status == StatusRunning {
 87            runningCount++
 88        }
 89    }
 90
 91    if runningCount >= pm.maxProcesses {
 92        return 0, fmt.Errorf("maximum processes (%d) reached", pm.maxProcesses)
 93    }
 94
 95    // Create managed process
 96    proc := &ManagedProcess{
 97        ID:     pm.nextID,
 98        Status: StatusStopped,
 99        Output: make(chan string, 100),
100        Error:  make(chan error, 10),
101    }
102
103    pm.nextID++
104
105    // Setup command
106    cmd := exec.Command(name, args...)
107    cmd.Env = append(os.Environ(), env...)
108
109    // Setup output capture
110    stdout, err := cmd.StdoutPipe()
111    if err != nil {
112        return 0, err
113    }
114
115    stderr, err := cmd.StderrPipe()
116    if err != nil {
117        return 0, err
118    }
119
120    proc.Cmd = cmd
121
122    // Start the process
123    if err := cmd.Start(); err != nil {
124        return 0, err
125    }
126
127    proc.Process = cmd.Process
128    proc.Status = StatusRunning
129    proc.StartTime = time.Now()
130
131    // Start output monitoring
132    pm.wg.Add(1)
133    go pm.monitorOutput(proc, stdout, stderr)
134
135    // Start process monitoring
136    pm.wg.Add(1)
137    go pm.monitorProcess(proc)
138
139    pm.processes[proc.ID] = proc
140
141    log.Printf("Started process %d: %s %v (PID: %d)", proc.ID, name, args, proc.Process.Pid)
142    return proc.ID, nil
143}
144
145// StopProcess stops a process gracefully
146func (pm *ProcessManager) StopProcess(id int, timeout time.Duration) error {
147    pm.mutex.Lock()
148    proc, exists := pm.processes[id]
149    pm.mutex.Unlock()
150
151    if !exists {
152        return fmt.Errorf("process %d not found", id)
153    }
154
155    proc.mutex.Lock()
156    defer proc.mutex.Unlock()
157
158    if proc.Status != StatusRunning {
159        return fmt.Errorf("process %d is not running", id)
160    }
161
162    proc.Status = StatusStopping
163
164    // Try graceful shutdown
165    if err := proc.Process.Signal(syscall.SIGTERM); err != nil {
166        return err
167    }
168
169    // Wait for graceful shutdown or timeout
170    done := make(chan error, 1)
171    go func() {
172        done <- proc.Cmd.Wait()
173    }()
174
175    select {
176    case err := <-done:
177        proc.Status = StatusStopped
178        return err
179    case <-time.After(timeout):
180        // Force kill if timeout
181        proc.Process.Kill()
182        proc.Status = StatusFailed
183        return fmt.Errorf("process %d killed after timeout", id)
184    }
185}
186
187// RestartProcess restarts a process
188func (pm *ProcessManager) RestartProcess(id int) error {
189    pm.mutex.Lock()
190    proc, exists := pm.processes[id]
191    pm.mutex.Unlock()
192
193    if !exists {
194        return fmt.Errorf("process %d not found", id)
195    }
196
197    proc.mutex.Lock()
198    if proc.Status == StatusRestarting {
199        proc.mutex.Unlock()
200        return fmt.Errorf("process %d is already restarting", id)
201    }
202
203    proc.Status = StatusRestarting
204    proc.RestartCount++
205    proc.mutex.Unlock()
206
207    // Stop the process
208    if err := pm.StopProcess(id, 10*time.Second); err != nil {
209        return err
210    }
211
212    // Restart with same command
213    cmd := proc.Cmd
214    if err := cmd.Start(); err != nil {
215        return err
216    }
217
218    proc.Process = cmd.Process
219    proc.Status = StatusRunning
220    proc.StartTime = time.Now()
221
222    log.Printf("Restarted process %d (restart count: %d)", id, proc.RestartCount)
223    return nil
224}
225
226// GetStatus returns the status of all processes
227func (pm *ProcessManager) GetStatus() map[int]ProcessInfo {
228    pm.mutex.RLock()
229    defer pm.mutex.RUnlock()
230
231    status := make(map[int]ProcessInfo)
232    for id, proc := range pm.processes {
233        proc.mutex.RLock()
234        info := ProcessInfo{
235            ID:           proc.ID,
236            Status:       proc.Status,
237            StartTime:    proc.StartTime,
238            RestartCount: proc.RestartCount,
239        }
240
241        if proc.Process != nil {
242            info.PID = proc.Process.Pid
243        }
244
245        proc.mutex.RUnlock()
246        status[id] = info
247    }
248
249    return status
250}
251
252// Shutdown stops all processes gracefully
253func (pm *ProcessManager) Shutdown() {
254    log.Println("Shutting down process manager...")
255
256    pm.mutex.Lock()
257    processes := make([]*ManagedProcess, 0, len(pm.processes))
258    for _, proc := range pm.processes {
259        processes = append(processes, proc)
260    }
261    pm.mutex.Unlock()
262
263    // Stop all processes
264    var wg sync.WaitGroup
265    for _, proc := range processes {
266        wg.Add(1)
267        go func(p *ManagedProcess) {
268            defer wg.Done()
269            if p.Status == StatusRunning {
270                pm.StopProcess(p.ID, 5*time.Second)
271            }
272        }(proc)
273    }
274
275    wg.Wait()
276
277    // Cancel context
278    pm.cancel()
279
280    // Wait for all goroutines
281    pm.wg.Wait()
282
283    log.Println("Process manager shutdown complete")
284}
285
286// monitorOutput captures process output
287func (pm *ProcessManager) monitorOutput(proc *ManagedProcess, stdout, stderr io.Reader) {
288    defer pm.wg.Done()
289
290    // Monitor stdout
291    go func() {
292        buf := make([]byte, 1024)
293        for {
294            n, err := stdout.Read(buf)
295            if err != nil {
296                return
297            }
298            select {
299            case proc.Output <- string(buf[:n]):
300            case <-pm.ctx.Done():
301                return
302            }
303        }
304    }()
305
306    // Monitor stderr
307    go func() {
308        buf := make([]byte, 1024)
309        for {
310            n, err := stderr.Read(buf)
311            if err != nil {
312                return
313            }
314            select {
315            case proc.Error <- fmt.Errorf(string(buf[:n])):
316            case <-pm.ctx.Done():
317                return
318            }
319        }
320    }()
321}
322
323// monitorProcess monitors process health
324func (pm *ProcessManager) monitorProcess(proc *ManagedProcess) {
325    defer pm.wg.Done()
326
327    ticker := time.NewTicker(1 * time.Second)
328    defer ticker.Stop()
329
330    for {
331        select {
332        case <-ticker.C:
333            // Check if process is still running
334            if proc.Process == nil {
335                continue
336            }
337
338            // Simulate health check
339            if time.Since(proc.StartTime) > 30*time.Second && proc.RestartCount == 0 {
340                log.Printf("Auto-restarting process %d for demonstration", proc.ID)
341                pm.RestartProcess(proc.ID)
342            }
343
344        case <-pm.ctx.Done():
345            return
346        }
347    }
348}
349
350// ProcessInfo contains process status information
351type ProcessInfo struct {
352    ID           int
353    PID          int
354    Status       ProcessStatus
355    StartTime    time.Time
356    RestartCount int
357}
358
359func main() {
360    // Example usage
361    pm := NewProcessManager(3)
362
363    // Start some processes
364    ids := make([]int, 0)
365
366    id1, _ := pm.StartProcess("sleep", []string{"10"}, nil)
367    ids = append(ids, id1)
368
369    id2, _ := pm.StartProcess("echo", []string{"Hello World"}, nil)
370    ids = append(ids, id2)
371
372    // Monitor status
373    go func() {
374        ticker := time.NewTicker(2 * time.Second)
375        defer ticker.Stop()
376
377        for {
378            select {
379            case <-ticker.C:
380                status := pm.GetStatus()
381                fmt.Printf("Process Status: %+v\n", status)
382            }
383        }
384    }()
385
386    // Let it run
387    time.Sleep(10 * time.Second)
388
389    // Shutdown
390    pm.Shutdown()
391}

Exercise 2: Configuration Management System

Learning Objectives: Build a comprehensive configuration system that handles environment variables, config files, validation, and hot-reloading.

Real-World Context: Modern applications need flexible configuration that adapts to different environments (dev, staging, prod) and can be updated without restarts. This pattern is used in production services at companies like Netflix, Uber, and Airbnb.

Difficulty: Intermediate | Time Estimate: 2-3 hours

Description: Create a configuration management system that loads from multiple sources (env vars, files, defaults), validates configuration, and supports hot-reloading without application restart.

Requirements:

  • Load from environment variables, JSON/YAML files, and defaults
  • Configuration validation with custom rules
  • Type-safe configuration access
  • Hot-reload on file change or SIGHUP signal
  • Configuration versioning and rollback
  • Encrypted secrets support
  • Configuration inheritance (base + environment-specific)
  • Logging of configuration changes
  • Thread-safe configuration access
  • Integration with popular config formats
Solution
  1package main
  2
  3import (
  4    "encoding/json"
  5    "fmt"
  6    "log"
  7    "os"
  8    "os/signal"
  9    "reflect"
 10    "strconv"
 11    "strings"
 12    "sync"
 13    "syscall"
 14    "time"
 15)
 16
 17// ConfigManager manages application configuration
 18type ConfigManager struct {
 19    config      interface{}
 20    configType  reflect.Type
 21    mutex       sync.RWMutex
 22    validators  []Validator
 23    watchers    []ConfigWatcher
 24    history     []ConfigSnapshot
 25    maxHistory  int
 26}
 27
 28// Validator validates configuration
 29type Validator func(interface{}) error
 30
 31// ConfigWatcher is notified of configuration changes
 32type ConfigWatcher interface {
 33    OnConfigChange(old, new interface{})
 34}
 35
 36// ConfigSnapshot stores configuration at a point in time
 37type ConfigSnapshot struct {
 38    Timestamp time.Time
 39    Config    interface{}
 40    Version   int
 41}
 42
 43// NewConfigManager creates a new configuration manager
 44func NewConfigManager(configType reflect.Type) *ConfigManager {
 45    return &ConfigManager{
 46        configType: configType,
 47        maxHistory: 10,
 48    }
 49}
 50
 51// Load loads configuration from multiple sources
 52func (cm *ConfigManager) Load() error {
 53    cm.mutex.Lock()
 54    defer cm.mutex.Unlock()
 55
 56    // Create new config instance
 57    config := reflect.New(cm.configType).Interface()
 58
 59    // 1. Load defaults
 60    if err := cm.loadDefaults(config); err != nil {
 61        return fmt.Errorf("failed to load defaults: %w", err)
 62    }
 63
 64    // 2. Load from file
 65    if configFile := os.Getenv("CONFIG_FILE"); configFile != "" {
 66        if err := cm.loadFromFile(config, configFile); err != nil {
 67            return fmt.Errorf("failed to load from file: %w", err)
 68        }
 69    }
 70
 71    // 3. Load from environment variables
 72    if err := cm.loadFromEnv(config); err != nil {
 73        return fmt.Errorf("failed to load from environment: %w", err)
 74    }
 75
 76    // 4. Validate configuration
 77    for _, validator := range cm.validators {
 78        if err := validator(config); err != nil {
 79            return fmt.Errorf("validation failed: %w", err)
 80        }
 81    }
 82
 83    // Save old config for watchers
 84    oldConfig := cm.config
 85
 86    // Update config
 87    cm.config = config
 88
 89    // Save snapshot
 90    cm.saveSnapshot(config)
 91
 92    // Notify watchers
 93    for _, watcher := range cm.watchers {
 94        watcher.OnConfigChange(oldConfig, config)
 95    }
 96
 97    log.Println("Configuration loaded successfully")
 98    return nil
 99}
100
101// loadDefaults loads default configuration values
102func (cm *ConfigManager) loadDefaults(config interface{}) error {
103    // Implement default loading logic
104    // For simplicity, we'll just set some defaults
105    return nil
106}
107
108// loadFromFile loads configuration from JSON file
109func (cm *ConfigManager) loadFromFile(config interface{}, filename string) error {
110    data, err := os.ReadFile(filename)
111    if err != nil {
112        return err
113    }
114
115    return json.Unmarshal(data, config)
116}
117
118// loadFromEnv loads configuration from environment variables
119func (cm *ConfigManager) loadFromEnv(config interface{}) error {
120    v := reflect.ValueOf(config).Elem()
121    t := v.Type()
122
123    for i := 0; i < v.NumField(); i++ {
124        field := v.Field(i)
125        fieldType := t.Field(i)
126
127        // Get environment variable name from tag or field name
128        envName := fieldType.Tag.Get("env")
129        if envName == "" {
130            envName = strings.ToUpper(fieldType.Name)
131        }
132
133        envValue := os.Getenv(envName)
134        if envValue == "" {
135            continue
136        }
137
138        // Set field value based on type
139        if err := cm.setFieldValue(field, envValue); err != nil {
140            return fmt.Errorf("failed to set %s: %w", fieldType.Name, err)
141        }
142    }
143
144    return nil
145}
146
147// setFieldValue sets field value from string
148func (cm *ConfigManager) setFieldValue(field reflect.Value, value string) error {
149    if !field.CanSet() {
150        return fmt.Errorf("field cannot be set")
151    }
152
153    switch field.Kind() {
154    case reflect.String:
155        field.SetString(value)
156    case reflect.Int, reflect.Int64:
157        intVal, err := strconv.ParseInt(value, 10, 64)
158        if err != nil {
159            return err
160        }
161        field.SetInt(intVal)
162    case reflect.Bool:
163        boolVal, err := strconv.ParseBool(value)
164        if err != nil {
165            return err
166        }
167        field.SetBool(boolVal)
168    case reflect.Slice:
169        if field.Type().Elem().Kind() == reflect.String {
170            field.Set(reflect.ValueOf(strings.Split(value, ",")))
171        }
172    default:
173        return fmt.Errorf("unsupported field type: %v", field.Kind())
174    }
175
176    return nil
177}
178
179// Get returns current configuration
180func (cm *ConfigManager) Get() interface{} {
181    cm.mutex.RLock()
182    defer cm.mutex.RUnlock()
183    return cm.config
184}
185
186// AddValidator adds a configuration validator
187func (cm *ConfigManager) AddValidator(validator Validator) {
188    cm.validators = append(cm.validators, validator)
189}
190
191// AddWatcher adds a configuration watcher
192func (cm *ConfigManager) AddWatcher(watcher ConfigWatcher) {
193    cm.watchers = append(cm.watchers, watcher)
194}
195
196// saveSnapshot saves configuration snapshot
197func (cm *ConfigManager) saveSnapshot(config interface{}) {
198    snapshot := ConfigSnapshot{
199        Timestamp: time.Now(),
200        Config:    config,
201        Version:   len(cm.history) + 1,
202    }
203
204    cm.history = append(cm.history, snapshot)
205
206    // Limit history size
207    if len(cm.history) > cm.maxHistory {
208        cm.history = cm.history[1:]
209    }
210}
211
212// Rollback rolls back to previous configuration
213func (cm *ConfigManager) Rollback(version int) error {
214    cm.mutex.Lock()
215    defer cm.mutex.Unlock()
216
217    for _, snapshot := range cm.history {
218        if snapshot.Version == version {
219            cm.config = snapshot.Config
220            log.Printf("Rolled back to version %d", version)
221            return nil
222        }
223    }
224
225    return fmt.Errorf("version %d not found", version)
226}
227
228// StartHotReload starts hot-reload on SIGHUP
229func (cm *ConfigManager) StartHotReload() {
230    sigChan := make(chan os.Signal, 1)
231    signal.Notify(sigChan, syscall.SIGHUP)
232
233    go func() {
234        for range sigChan {
235            log.Println("Received SIGHUP, reloading configuration")
236            if err := cm.Load(); err != nil {
237                log.Printf("Failed to reload configuration: %v", err)
238            }
239        }
240    }()
241}
242
243// Example configuration structure
244type AppConfig struct {
245    Port         int      `env:"PORT" json:"port"`
246    Debug        bool     `env:"DEBUG" json:"debug"`
247    DatabaseURL  string   `env:"DATABASE_URL" json:"database_url"`
248    AllowedHosts []string `env:"ALLOWED_HOSTS" json:"allowed_hosts"`
249    Timeout      int      `env:"TIMEOUT" json:"timeout"`
250}
251
252// Example watcher
253type ConfigLogger struct{}
254
255func (cl *ConfigLogger) OnConfigChange(old, new interface{}) {
256    log.Printf("Configuration changed from %+v to %+v", old, new)
257}
258
259func main() {
260    // Create config manager
261    configType := reflect.TypeOf(AppConfig{})
262    cm := NewConfigManager(configType)
263
264    // Add validators
265    cm.AddValidator(func(config interface{}) error {
266        appConfig := config.(*AppConfig)
267        if appConfig.Port < 1 || appConfig.Port > 65535 {
268            return fmt.Errorf("invalid port: %d", appConfig.Port)
269        }
270        if appConfig.Timeout < 0 {
271            return fmt.Errorf("timeout cannot be negative")
272        }
273        return nil
274    })
275
276    // Add watchers
277    cm.AddWatcher(&ConfigLogger{})
278
279    // Load initial configuration
280    if err := cm.Load(); err != nil {
281        log.Fatalf("Failed to load configuration: %v", err)
282    }
283
284    // Start hot-reload
285    cm.StartHotReload()
286
287    // Get current config
288    config := cm.Get().(*AppConfig)
289    fmt.Printf("Current configuration: %+v\n", config)
290
291    // Keep running
292    select {}
293}

Exercise 3: System Resource Monitor

Learning Objectives: Learn to monitor system resources, collect metrics, and implement health checks for production applications.

Real-World Context: Production applications need to monitor CPU, memory, disk, and network usage to ensure healthy operation. This is essential for auto-scaling, alerting, and troubleshooting.

Difficulty: Intermediate | Time Estimate: 2-3 hours

Description: Create a system resource monitor that tracks CPU, memory, disk, and process metrics, provides health checks, and exports metrics for monitoring systems.

Requirements:

  • CPU usage monitoring (per-core and overall)
  • Memory usage tracking (RSS, VMS, heap)
  • Disk I/O and space monitoring
  • Network statistics
  • Process-specific metrics
  • Health check endpoints
  • Metric export (Prometheus format)
  • Historical data with retention
  • Alerting thresholds
  • Cross-platform support
Solution
  1package main
  2
  3import (
  4    "fmt"
  5    "os"
  6    "runtime"
  7    "sync"
  8    "time"
  9)
 10
 11// ResourceMonitor monitors system resources
 12type ResourceMonitor struct {
 13    interval time.Duration
 14    metrics  *Metrics
 15    mutex    sync.RWMutex
 16    stopChan chan struct{}
 17}
 18
 19// Metrics contains system metrics
 20type Metrics struct {
 21    CPU     CPUMetrics
 22    Memory  MemoryMetrics
 23    Disk    DiskMetrics
 24    Process ProcessMetrics
 25}
 26
 27// CPUMetrics contains CPU usage information
 28type CPUMetrics struct {
 29    UsagePercent float64
 30    NumCPU       int
 31    NumGoroutine int
 32}
 33
 34// MemoryMetrics contains memory usage information
 35type MemoryMetrics struct {
 36    Alloc        uint64
 37    TotalAlloc   uint64
 38    Sys          uint64
 39    NumGC        uint32
 40    HeapAlloc    uint64
 41    HeapSys      uint64
 42    HeapInuse    uint64
 43}
 44
 45// DiskMetrics contains disk usage information
 46type DiskMetrics struct {
 47    TotalBytes uint64
 48    UsedBytes  uint64
 49    FreeBytes  uint64
 50}
 51
 52// ProcessMetrics contains process-specific metrics
 53type ProcessMetrics struct {
 54    PID          int
 55    NumThreads   int
 56    OpenFiles    int
 57    StartTime    time.Time
 58    UptimeSeconds float64
 59}
 60
 61// NewResourceMonitor creates a new resource monitor
 62func NewResourceMonitor(interval time.Duration) *ResourceMonitor {
 63    return &ResourceMonitor{
 64        interval: interval,
 65        metrics:  &Metrics{},
 66        stopChan: make(chan struct{}),
 67    }
 68}
 69
 70// Start starts the resource monitor
 71func (rm *ResourceMonitor) Start() {
 72    ticker := time.NewTicker(rm.interval)
 73    go func() {
 74        for {
 75            select {
 76            case <-ticker.C:
 77                rm.collect()
 78            case <-rm.stopChan:
 79                ticker.Stop()
 80                return
 81            }
 82        }
 83    }()
 84}
 85
 86// Stop stops the resource monitor
 87func (rm *ResourceMonitor) Stop() {
 88    close(rm.stopChan)
 89}
 90
 91// collect collects all metrics
 92func (rm *ResourceMonitor) collect() {
 93    rm.mutex.Lock()
 94    defer rm.mutex.Unlock()
 95
 96    rm.collectCPUMetrics()
 97    rm.collectMemoryMetrics()
 98    rm.collectProcessMetrics()
 99}
100
101// collectCPUMetrics collects CPU metrics
102func (rm *ResourceMonitor) collectCPUMetrics() {
103    rm.metrics.CPU.NumCPU = runtime.NumCPU()
104    rm.metrics.CPU.NumGoroutine = runtime.NumGoroutine()
105    // In production, use third-party libraries like gopsutil for accurate CPU usage
106    rm.metrics.CPU.UsagePercent = 0.0
107}
108
109// collectMemoryMetrics collects memory metrics
110func (rm *ResourceMonitor) collectMemoryMetrics() {
111    var m runtime.MemStats
112    runtime.ReadMemStats(&m)
113
114    rm.metrics.Memory.Alloc = m.Alloc
115    rm.metrics.Memory.TotalAlloc = m.TotalAlloc
116    rm.metrics.Memory.Sys = m.Sys
117    rm.metrics.Memory.NumGC = m.NumGC
118    rm.metrics.Memory.HeapAlloc = m.HeapAlloc
119    rm.metrics.Memory.HeapSys = m.HeapSys
120    rm.metrics.Memory.HeapInuse = m.HeapInuse
121}
122
123// collectProcessMetrics collects process metrics
124func (rm *ResourceMonitor) collectProcessMetrics() {
125    rm.metrics.Process.PID = os.Getpid()
126    rm.metrics.Process.NumThreads = runtime.NumGoroutine()
127    // Calculate uptime if StartTime is set
128    if !rm.metrics.Process.StartTime.IsZero() {
129        rm.metrics.Process.UptimeSeconds = time.Since(rm.metrics.Process.StartTime).Seconds()
130    } else {
131        rm.metrics.Process.StartTime = time.Now()
132    }
133}
134
135// GetMetrics returns current metrics
136func (rm *ResourceMonitor) GetMetrics() Metrics {
137    rm.mutex.RLock()
138    defer rm.mutex.RUnlock()
139    return *rm.metrics
140}
141
142// FormatPrometheus formats metrics in Prometheus format
143func (rm *ResourceMonitor) FormatPrometheus() string {
144    metrics := rm.GetMetrics()
145
146    return fmt.Sprintf(`# HELP cpu_num_cpu Number of CPUs
147# TYPE cpu_num_cpu gauge
148cpu_num_cpu %d
149
150# HELP cpu_num_goroutine Number of goroutines
151# TYPE cpu_num_goroutine gauge
152cpu_num_goroutine %d
153
154# HELP memory_alloc Allocated memory in bytes
155# TYPE memory_alloc gauge
156memory_alloc %d
157
158# HELP memory_total_alloc Total allocated memory in bytes
159# TYPE memory_total_alloc counter
160memory_total_alloc %d
161
162# HELP memory_sys System memory in bytes
163# TYPE memory_sys gauge
164memory_sys %d
165
166# HELP memory_num_gc Number of GC cycles
167# TYPE memory_num_gc counter
168memory_num_gc %d
169
170# HELP process_uptime_seconds Process uptime in seconds
171# TYPE process_uptime_seconds gauge
172process_uptime_seconds %.2f
173`,
174        metrics.CPU.NumCPU,
175        metrics.CPU.NumGoroutine,
176        metrics.Memory.Alloc,
177        metrics.Memory.TotalAlloc,
178        metrics.Memory.Sys,
179        metrics.Memory.NumGC,
180        metrics.Process.UptimeSeconds,
181    )
182}
183
184// HealthCheck performs health check
185func (rm *ResourceMonitor) HealthCheck() (bool, string) {
186    metrics := rm.GetMetrics()
187
188    // Check goroutine count
189    if metrics.CPU.NumGoroutine > 10000 {
190        return false, "Too many goroutines"
191    }
192
193    // Check memory usage
194    if metrics.Memory.Alloc > 1024*1024*1024 { // 1GB
195        return false, "High memory usage"
196    }
197
198    return true, "Healthy"
199}
200
201func main() {
202    // Create and start monitor
203    monitor := NewResourceMonitor(5 * time.Second)
204    monitor.Start()
205    defer monitor.Stop()
206
207    // Print metrics periodically
208    for i := 0; i < 5; i++ {
209        time.Sleep(5 * time.Second)
210
211        metrics := monitor.GetMetrics()
212        fmt.Printf("\n=== System Metrics ===\n")
213        fmt.Printf("CPU: %d cores, %d goroutines\n",
214            metrics.CPU.NumCPU, metrics.CPU.NumGoroutine)
215        fmt.Printf("Memory: Alloc=%d MB, Sys=%d MB, NumGC=%d\n",
216            metrics.Memory.Alloc/1024/1024,
217            metrics.Memory.Sys/1024/1024,
218            metrics.Memory.NumGC)
219        fmt.Printf("Process: PID=%d, Uptime=%.2fs\n",
220            metrics.Process.PID, metrics.Process.UptimeSeconds)
221
222        // Health check
223        healthy, message := monitor.HealthCheck()
224        fmt.Printf("Health: %t - %s\n", healthy, message)
225    }
226
227    // Export Prometheus metrics
228    fmt.Printf("\n=== Prometheus Metrics ===\n")
229    fmt.Println(monitor.FormatPrometheus())
230}

Exercise 4: Log Rotation and Management

Learning Objectives: Implement production-grade logging with rotation, compression, and retention policies.

Real-World Context: Production applications generate gigabytes of logs. Proper log management prevents disk space issues, enables debugging, and supports compliance requirements.

Difficulty: Intermediate | Time Estimate: 2-3 hours

Description: Create a log rotation system that automatically rotates log files based on size or time, compresses old logs, and enforces retention policies.

Requirements:

  • Size-based rotation (rotate when file exceeds threshold)
  • Time-based rotation (daily, hourly)
  • Compression of rotated logs
  • Retention policy (delete logs older than N days)
  • Log file naming with timestamps
  • Atomic rotation (no log loss)
  • Concurrent write safety
  • Configurable via environment variables
  • Signal handling for manual rotation
  • Integration with standard logger
Solution
  1package main
  2
  3import (
  4    "compress/gzip"
  5    "fmt"
  6    "io"
  7    "log"
  8    "os"
  9    "path/filepath"
 10    "sort"
 11    "sync"
 12    "time"
 13)
 14
 15// RotatingLogger provides log rotation functionality
 16type RotatingLogger struct {
 17    filename       string
 18    maxSize        int64
 19    maxAge         int
 20    compress       bool
 21    currentFile    *os.File
 22    currentSize    int64
 23    mutex          sync.Mutex
 24}
 25
 26// NewRotatingLogger creates a new rotating logger
 27func NewRotatingLogger(filename string, maxSize int64, maxAge int, compress bool) (*RotatingLogger, error) {
 28    rl := &RotatingLogger{
 29        filename: filename,
 30        maxSize:  maxSize,
 31        maxAge:   maxAge,
 32        compress: compress,
 33    }
 34
 35    if err := rl.openFile(); err != nil {
 36        return nil, err
 37    }
 38
 39    return rl, nil
 40}
 41
 42// Write writes data to the log file
 43func (rl *RotatingLogger) Write(p []byte) (n int, err error) {
 44    rl.mutex.Lock()
 45    defer rl.mutex.Unlock()
 46
 47    // Check if rotation is needed
 48    if rl.currentSize+int64(len(p)) > rl.maxSize {
 49        if err := rl.rotate(); err != nil {
 50            return 0, err
 51        }
 52    }
 53
 54    n, err = rl.currentFile.Write(p)
 55    rl.currentSize += int64(n)
 56    return n, err
 57}
 58
 59// openFile opens the log file
 60func (rl *RotatingLogger) openFile() error {
 61    file, err := os.OpenFile(rl.filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
 62    if err != nil {
 63        return err
 64    }
 65
 66    info, err := file.Stat()
 67    if err != nil {
 68        file.Close()
 69        return err
 70    }
 71
 72    rl.currentFile = file
 73    rl.currentSize = info.Size()
 74    return nil
 75}
 76
 77// rotate rotates the log file
 78func (rl *RotatingLogger) rotate() error {
 79    // Close current file
 80    if rl.currentFile != nil {
 81        if err := rl.currentFile.Close(); err != nil {
 82            return err
 83        }
 84    }
 85
 86    // Generate rotation filename
 87    timestamp := time.Now().Format("20060102-150405")
 88    rotatedName := fmt.Sprintf("%s.%s", rl.filename, timestamp)
 89
 90    // Rename current file
 91    if err := os.Rename(rl.filename, rotatedName); err != nil {
 92        return err
 93    }
 94
 95    // Compress if enabled
 96    if rl.compress {
 97        go rl.compressFile(rotatedName)
 98    }
 99
100    // Clean up old logs
101    go rl.cleanup()
102
103    // Open new file
104    return rl.openFile()
105}
106
107// compressFile compresses a log file
108func (rl *RotatingLogger) compressFile(filename string) error {
109    // Open source file
110    src, err := os.Open(filename)
111    if err != nil {
112        return err
113    }
114    defer src.Close()
115
116    // Create compressed file
117    dst, err := os.Create(filename + ".gz")
118    if err != nil {
119        return err
120    }
121    defer dst.Close()
122
123    // Compress
124    writer := gzip.NewWriter(dst)
125    defer writer.Close()
126
127    if _, err := io.Copy(writer, src); err != nil {
128        return err
129    }
130
131    // Remove original file
132    return os.Remove(filename)
133}
134
135// cleanup removes old log files
136func (rl *RotatingLogger) cleanup() error {
137    if rl.maxAge <= 0 {
138        return nil
139    }
140
141    dir := filepath.Dir(rl.filename)
142    base := filepath.Base(rl.filename)
143
144    // Find all rotated log files
145    pattern := filepath.Join(dir, base+".*")
146    matches, err := filepath.Glob(pattern)
147    if err != nil {
148        return err
149    }
150
151    cutoff := time.Now().AddDate(0, 0, -rl.maxAge)
152
153    for _, match := range matches {
154        info, err := os.Stat(match)
155        if err != nil {
156            continue
157        }
158
159        if info.ModTime().Before(cutoff) {
160            log.Printf("Removing old log file: %s", match)
161            os.Remove(match)
162        }
163    }
164
165    return nil
166}
167
168// Close closes the logger
169func (rl *RotatingLogger) Close() error {
170    rl.mutex.Lock()
171    defer rl.mutex.Unlock()
172
173    if rl.currentFile != nil {
174        return rl.currentFile.Close()
175    }
176    return nil
177}
178
179// GetLogFiles returns list of all log files
180func (rl *RotatingLogger) GetLogFiles() ([]string, error) {
181    dir := filepath.Dir(rl.filename)
182    base := filepath.Base(rl.filename)
183
184    pattern := filepath.Join(dir, base+"*")
185    matches, err := filepath.Glob(pattern)
186    if err != nil {
187        return nil, err
188    }
189
190    sort.Strings(matches)
191    return matches, nil
192}
193
194func main() {
195    // Create rotating logger
196    logger, err := NewRotatingLogger(
197        "/tmp/app.log",
198        1024*1024,    // 1MB max size
199        7,            // Keep logs for 7 days
200        true,         // Compress rotated logs
201    )
202    if err != nil {
203        log.Fatalf("Failed to create logger: %v", err)
204    }
205    defer logger.Close()
206
207    // Use with standard log package
208    log.SetOutput(logger)
209    log.SetFlags(log.LstdFlags | log.Lshortfile)
210
211    // Generate some logs
212    for i := 0; i < 100; i++ {
213        log.Printf("Log message %d: This is a test log entry", i)
214        time.Sleep(100 * time.Millisecond)
215    }
216
217    // List log files
218    files, _ := logger.GetLogFiles()
219    fmt.Println("\nLog files:")
220    for _, file := range files {
221        fmt.Printf("  - %s\n", file)
222    }
223}

Exercise 5: Distributed Lock Manager

Learning Objectives: Implement distributed coordination using file locks for multi-process synchronization.

Real-World Context: Multiple processes or instances of an application need to coordinate access to shared resources. File-based locks are simple, reliable, and don't require external services.

Difficulty: Advanced | Time Estimate: 3-4 hours

Description: Create a distributed lock manager using file system locks that allows multiple processes to coordinate access to shared resources with proper timeout and deadlock detection.

Requirements:

  • Exclusive and shared lock modes
  • Lock timeout and automatic release
  • Deadlock detection
  • Lock queuing and fairness
  • Lock health monitoring
  • Automatic cleanup of stale locks
  • Process crash recovery
  • Cross-platform support
  • Lock statistics and monitoring
  • Integration tests with multiple processes
Solution
  1package main
  2
  3import (
  4    "fmt"
  5    "os"
  6    "path/filepath"
  7    "sync"
  8    "time"
  9)
 10
 11// LockManager manages distributed locks using file system
 12type LockManager struct {
 13    lockDir   string
 14    locks     map[string]*Lock
 15    mutex     sync.Mutex
 16}
 17
 18// Lock represents a distributed lock
 19type Lock struct {
 20    name       string
 21    filename   string
 22    file       *os.File
 23    holderPID  int
 24    acquiredAt time.Time
 25    timeout    time.Duration
 26}
 27
 28// LockMode defines lock mode
 29type LockMode int
 30
 31const (
 32    LockModeExclusive LockMode = iota
 33    LockModeShared
 34)
 35
 36// NewLockManager creates a new lock manager
 37func NewLockManager(lockDir string) (*LockManager, error) {
 38    if err := os.MkdirAll(lockDir, 0755); err != nil {
 39        return nil, err
 40    }
 41
 42    lm := &LockManager{
 43        lockDir: lockDir,
 44        locks:   make(map[string]*Lock),
 45    }
 46
 47    // Start cleanup goroutine
 48    go lm.cleanupStale()
 49
 50    return lm, nil
 51}
 52
 53// Acquire acquires a lock
 54func (lm *LockManager) Acquire(name string, timeout time.Duration) (*Lock, error) {
 55    lm.mutex.Lock()
 56    defer lm.mutex.Unlock()
 57
 58    // Check if already locked
 59    if lock, exists := lm.locks[name]; exists {
 60        return lock, fmt.Errorf("lock already held")
 61    }
 62
 63    // Create lock file
 64    filename := filepath.Join(lm.lockDir, name+".lock")
 65    file, err := os.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
 66    if err != nil {
 67        return nil, fmt.Errorf("failed to acquire lock: %w", err)
 68    }
 69
 70    // Write PID to lock file
 71    pid := os.Getpid()
 72    fmt.Fprintf(file, "%d\n%s\n", pid, time.Now().Format(time.RFC3339))
 73
 74    lock := &Lock{
 75        name:       name,
 76        filename:   filename,
 77        file:       file,
 78        holderPID:  pid,
 79        acquiredAt: time.Now(),
 80        timeout:    timeout,
 81    }
 82
 83    lm.locks[name] = lock
 84
 85    // Start timeout monitor
 86    if timeout > 0 {
 87        go lm.monitorTimeout(lock)
 88    }
 89
 90    return lock, nil
 91}
 92
 93// Release releases a lock
 94func (lm *LockManager) Release(lock *Lock) error {
 95    lm.mutex.Lock()
 96    defer lm.mutex.Unlock()
 97
 98    if lock.file != nil {
 99        lock.file.Close()
100    }
101
102    os.Remove(lock.filename)
103    delete(lm.locks, lock.name)
104
105    return nil
106}
107
108// TryAcquire tries to acquire lock with timeout
109func (lm *LockManager) TryAcquire(name string, lockTimeout, waitTimeout time.Duration) (*Lock, error) {
110    deadline := time.Now().Add(waitTimeout)
111
112    for {
113        lock, err := lm.Acquire(name, lockTimeout)
114        if err == nil {
115            return lock, nil
116        }
117
118        if time.Now().After(deadline) {
119            return nil, fmt.Errorf("timeout waiting for lock")
120        }
121
122        time.Sleep(100 * time.Millisecond)
123    }
124}
125
126// monitorTimeout monitors lock timeout
127func (lm *LockManager) monitorTimeout(lock *Lock) {
128    time.Sleep(lock.timeout)
129
130    lm.mutex.Lock()
131    defer lm.mutex.Unlock()
132
133    // Check if lock is still held
134    if currentLock, exists := lm.locks[lock.name]; exists && currentLock == lock {
135        fmt.Printf("Lock %s timed out, releasing\n", lock.name)
136        lm.Release(lock)
137    }
138}
139
140// cleanupStale cleans up stale locks from dead processes
141func (lm *LockManager) cleanupStale() {
142    ticker := time.NewTicker(10 * time.Second)
143    defer ticker.Stop()
144
145    for range ticker.C {
146        lm.mutex.Lock()
147
148        matches, _ := filepath.Glob(filepath.Join(lm.lockDir, "*.lock"))
149        for _, filename := range matches {
150            if lm.isStale(filename) {
151                fmt.Printf("Removing stale lock: %s\n", filename)
152                os.Remove(filename)
153            }
154        }
155
156        lm.mutex.Unlock()
157    }
158}
159
160// isStale checks if lock file is stale
161func (lm *LockManager) isStale(filename string) bool {
162    info, err := os.Stat(filename)
163    if err != nil {
164        return true
165    }
166
167    // Consider stale if older than 5 minutes
168    return time.Since(info.ModTime()) > 5*time.Minute
169}
170
171// GetActiveLocks returns list of active locks
172func (lm *LockManager) GetActiveLocks() []string {
173    lm.mutex.Lock()
174    defer lm.mutex.Unlock()
175
176    locks := make([]string, 0, len(lm.locks))
177    for name := range lm.locks {
178        locks = append(locks, name)
179    }
180    return locks
181}
182
183func main() {
184    // Create lock manager
185    lm, err := NewLockManager("/tmp/locks")
186    if err != nil {
187        fmt.Printf("Error creating lock manager: %v\n", err)
188        return
189    }
190
191    // Acquire lock
192    lock, err := lm.Acquire("resource-1", 10*time.Second)
193    if err != nil {
194        fmt.Printf("Failed to acquire lock: %v\n", err)
195        return
196    }
197
198    fmt.Printf("Lock acquired: %s (PID: %d)\n", lock.name, lock.holderPID)
199
200    // Do work
201    time.Sleep(2 * time.Second)
202
203    // Release lock
204    if err := lm.Release(lock); err != nil {
205        fmt.Printf("Failed to release lock: %v\n", err)
206        return
207    }
208
209    fmt.Println("Lock released")
210
211    // Try acquire with timeout
212    lock2, err := lm.TryAcquire("resource-2", 5*time.Second, 3*time.Second)
213    if err != nil {
214        fmt.Printf("Failed to try acquire: %v\n", err)
215        return
216    }
217
218    fmt.Printf("Lock acquired: %s\n", lock2.name)
219    lm.Release(lock2)
220
221    // Show active locks
222    active := lm.GetActiveLocks()
223    fmt.Printf("Active locks: %v\n", active)
224}

Further Reading

Practice Exercises

This article provides the foundation for building production-ready applications that interact properly with the operating system. The exercises guide you through implementing real-world patterns like process managers, signal handlers, and configuration systems that are essential for professional Go development.

The next step is to practice these concepts by building increasingly complex applications that demonstrate proper OS integration patterns in action.

Summary

Key Takeaways

  1. Signal Handling: Always implement graceful shutdown for production applications
  2. Environment Variables: Use them for configuration, not secrets
  3. Process Management: Understand when to use os/exec vs. goroutines
  4. Cross-Platform: Test on all target platforms
  5. Resource Cleanup: Always clean up resources and child processes
  6. Error Handling: Handle process failures and timeouts properly
  7. Security: Never trust user input in process execution

Best Practices

  1. Graceful Shutdown: Always handle SIGINT and SIGTERM
  2. Timeouts: Set reasonable timeouts for all operations
  3. Resource Limits: Limit concurrent processes and memory usage
  4. Logging: Log process starts, stops, and errors
  5. Health Checks: Implement process health monitoring
  6. Configuration: Use environment variables for deployment-specific settings
  7. Error Recovery: Implement restart logic for critical processes

When to Use What

  • os.Getenv(): Simple configuration values
  • os/signal: Graceful shutdown handling
  • os/exec: External tool integration and process spawning
  • syscall: Low-level OS interactions when high-level APIs aren't sufficient
  • os/user: User authentication and permissions
  • context.Context: Timeout and cancellation management

Operating system integration transforms your Go applications from simple programs into robust, production-ready services that can handle the complexities of real-world deployment environments.