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:
- Handle System Signals: Implement graceful shutdown for applications
- Manage Processes: Create and control child processes from Go programs
- Work with Environment: Load and validate configuration from environment variables
- Integrate with OS: Use file system operations, user management, and system resources
- Build Production Patterns: Implement daemon processes, health checks, and monitoring
- Handle Cross-Platform: Write code that works across Windows, Linux, and macOS
- 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:
- Program Initialization: OS loads your executable, allocates memory, sets up environment
- Resource Requests: Your program asks for files, network sockets, memory, or other resources
- System Communication: OS sends signals and notifications about system events
- Process Lifecycle: Your program starts, runs, potentially spawns children, then exits
- 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:
ospackage: High-level, portable OS operationssyscallpackage: Low-level, platform-specific system callsos/execpackage: Process management and executionos/signalpackage: Signal handling and notificationos/userpackage: 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
- Signal Handling: Always implement graceful shutdown for production applications
- Environment Variables: Use them for configuration, not secrets
- Process Management: Understand when to use
os/execvs. goroutines - Cross-Platform: Test on all target platforms
- Resource Cleanup: Always clean up resources and child processes
- Error Handling: Handle process failures and timeouts properly
- Security: Never trust user input in process execution
Best Practices
- Graceful Shutdown: Always handle SIGINT and SIGTERM
- Timeouts: Set reasonable timeouts for all operations
- Resource Limits: Limit concurrent processes and memory usage
- Logging: Log process starts, stops, and errors
- Health Checks: Implement process health monitoring
- Configuration: Use environment variables for deployment-specific settings
- Error Recovery: Implement restart logic for critical processes
When to Use What
os.Getenv(): Simple configuration valuesos/signal: Graceful shutdown handlingos/exec: External tool integration and process spawningsyscall: Low-level OS interactions when high-level APIs aren't sufficientos/user: User authentication and permissionscontext.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.