unix

package
v1.22.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2026 License: MIT Imports: 21 Imported by: 0

README

Unix Socket Server Package

License Go Version Coverage Platform

Production-ready Unix domain socket server for local inter-process communication (IPC) with file permissions, graceful shutdown, connection lifecycle management, and comprehensive monitoring capabilities.


Table of Contents


Overview

The unix package provides a high-performance, production-ready Unix domain socket server for local inter-process communication (IPC). It implements a goroutine-per-connection model optimized for hundreds to thousands of concurrent connections with file permissions control and zero network overhead.

Design Philosophy
  1. Local IPC First: Optimized for same-host communication with minimal overhead
  2. Production Ready: Built-in monitoring, error handling, and graceful shutdown
  3. Security via Filesystem: File permissions and group ownership for access control
  4. Observable: Real-time connection tracking and lifecycle callbacks
  5. Context-Aware: Full integration with Go's context for cancellation and timeouts
Key Features
  • Unix Domain Sockets: SOCK_STREAM for reliable local IPC
  • File Permissions: Configurable permissions (0600, 0660, 0666, etc.)
  • Group Ownership: Fine-grained access control via group ID
  • Graceful Shutdown: Connection draining with configurable timeouts
  • Connection Tracking: Real-time connection counting and monitoring
  • Idle Timeout: Automatic cleanup of inactive connections
  • Lifecycle Callbacks: Hook into connection events (new, read, write, close)
  • Thread-Safe: Lock-free atomic operations for state management
  • Context Integration: Full context support for cancellation and deadlines
  • Platform-Specific: Linux and macOS only (not Windows)

Architecture

Component Diagram
┌─────────────────────────────────────────────────────┐
│              Unix Socket Server                     │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ┌──────────────┐       ┌───────────────────┐       │
│  │   Listener   │       │  Context Manager  │       │
│  │ (net.Unix)   │       │  (cancellation)   │       │
│  └──────┬───────┘       └─────────┬─────────┘       │
│         │                         │                 │
│         ▼                         ▼                 │
│  ┌──────────────────────────────────────────┐       │
│  │       Connection Accept Loop             │       │
│  │     (with file permissions setup)        │       │
│  └──────────────┬───────────────────────────┘       │
│                 │                                   │
│                 ▼                                   │
│         Per-Connection Goroutine                    │
│         ┌─────────────────────┐                     │
│         │  sCtx (I/O wrapper) │                     │
│         │   - Read/Write      │                     │
│         │   - Idle timeout    │                     │
│         │   - State tracking  │                     │
│         └──────────┬──────────┘                     │
│                    │                                │
│                    ▼                                │
│         ┌─────────────────────┐                     │
│         │   User Handler      │                     │
│         │   (custom logic)    │                     │
│         └─────────────────────┘                     │
│                                                     │
│  Socket File Management:                            │
│   - File permissions (chmod)                        │
│   - Group ownership (chown)                         │
│   - Automatic cleanup on shutdown                   │
│                                                     │
│  Optional Callbacks:                                │
│   - UpdateConn: Connection configuration            │
│   - FuncError: Error reporting                      │
│   - FuncInfo: Connection events                     │
│   - FuncInfoSrv: Server lifecycle                   │
│                                                     │
└─────────────────────────────────────────────────────┘
Data Flow
  1. Server Start: Listen() creates Unix listener with file permissions
  2. Socket File Setup: Creates socket file, sets permissions and group ownership
  3. Accept Loop: Continuously accepts new connections
  4. Connection Setup:
    • Connection counter incremented
    • UpdateConn callback invoked
    • Connection wrapped in sCtx context
    • Handler goroutine spawned
    • Idle timeout monitoring started
  5. Handler Execution: User handler processes the connection
  6. Connection Close:
    • Connection closed
    • Context cancelled
    • Counter decremented
    • Goroutine cleaned up
  7. Server Shutdown: Socket file automatically removed
Lifecycle States
┌─────────────┐
│  Created    │  IsRunning=false, IsGone=false
└──────┬──────┘
       │ Listen()
       ▼
┌─────────────┐
│  Running    │  IsRunning=true, IsGone=false
└──────┬──────┘  (Accepting connections)
       │ Shutdown()
       ▼
┌─────────────┐
│  Draining   │  IsRunning=false, IsGone=true
└──────┬──────┘  (Waiting for connections to close)
       │ All connections closed
       ▼
┌─────────────┐
│  Stopped    │  IsRunning=false, IsGone=true
└─────────────┘  (All resources released)

Performance

Throughput

Based on benchmarks with echo server on same host:

Configuration Connections Throughput Latency (P50)
Unix Socket 100 ~1M req/s <500 µs
Unix Socket 1000 ~900K req/s <1 ms
TCP Loopback 100 ~500K req/s 1-2 ms
TCP Loopback 1000 ~450K req/s 2-3 ms

Unix sockets are 2-5x faster than TCP loopback for local IPC Actual throughput depends on handler complexity and system load

Memory Usage

Per-connection memory footprint:

Goroutine stack:      ~8 KB
sCtx structure:       ~1 KB
Application buffers:  Variable (e.g., 4 KB)
────────────────────────────
Total per connection: ~10-15 KB

Memory scaling examples:

  • 100 connections: ~1-2 MB
  • 1,000 connections: ~10-15 MB
  • 10,000 connections: ~100-150 MB
Scalability

Recommended connection limits:

Connections Performance Notes
1-1,000 Excellent Ideal range
1,000-5,000 Good Monitor memory and file descriptors
5,000-10,000 Fair Consider profiling
10,000+ Not advised Event-driven model recommended

Use Cases

1. Microservice Communication

Problem: Fast, secure communication between services on the same host.

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    
    // Read length-prefixed messages
    lenBuf := make([]byte, 4)
    if _, err := io.ReadFull(ctx, lenBuf); err != nil {
        return
    }
    
    msgLen := binary.BigEndian.Uint32(lenBuf)
    msg := make([]byte, msgLen)
    if _, err := io.ReadFull(ctx, msg); err != nil {
        return
    }
    
    // Process and respond
    response := processMessage(msg)
    ctx.Write(response)
}

Real-world: Docker daemon, systemd services, container orchestration.

2. Application Plugin System

Problem: Communication between main application and plugins with security.

cfg := sckcfg.Server{
    Network:  libptc.NetworkUnix,
    Address:  "/var/run/myapp/plugins.sock",
    PermFile: libprm.New(0600),  // Owner only
    GroupPerm: -1,
    ConIdleTimeout: 5 * time.Minute,
}

srv, _ := unix.New(nil, pluginHandler, cfg)

Real-world: Application frameworks, plugin architectures, extension systems.

3. Database Proxy

Problem: Local proxy for database connection pooling and monitoring.

var backendPool sync.Pool

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    
    // Get backend connection from pool
    backend := backendPool.Get().(net.Conn)
    defer backendPool.Put(backend)
    
    // Bidirectional copy
    go io.Copy(backend, ctx)
    io.Copy(ctx, backend)
}

Real-world: PostgreSQL pgBouncer alternative, MySQL proxy, Redis connection pooler.

4. Process Monitoring and Control

Problem: Control interface for long-running daemon processes.

srv.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
    switch state {
    case libsck.ConnectionNew:
        log.Printf("Admin connected: %s", remote)
    case libsck.ConnectionClose:
        log.Printf("Admin disconnected: %s", remote)
    }
})

Real-world: System daemons, background workers, service managers.

5. Local IPC for GUI Applications

Problem: Communication between GUI frontend and backend service.

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    
    for {
        // Read frame header
        header := make([]byte, 2)
        if _, err := io.ReadFull(ctx, header); err != nil {
            return
        }
        
        opcode := header[0]
        payloadLen := header[1]
        
        // Read payload
        payload := make([]byte, payloadLen)
        if _, err := io.ReadFull(ctx, payload); err != nil {
            return
        }
        
        processFrame(opcode, payload)
    }
}

Real-world: Electron apps, desktop applications, system tray utilities.


Quick Start

Installation
go get github.com/nabbar/golib/socket/server/unix
Basic Echo Server
package main

import (
    "context"
    "io"
    
    libptc "github.com/nabbar/golib/network/protocol"
    libprm "github.com/nabbar/golib/file/perm"
    libsck "github.com/nabbar/golib/socket"
    sckcfg "github.com/nabbar/golib/socket/config"
    unix "github.com/nabbar/golib/socket/server/unix"
)

func main() {
    // Define echo handler
    handler := func(ctx libsck.Context) {
        defer ctx.Close()
        io.Copy(ctx, ctx)  // Echo
    }
    
    // Create configuration
    cfg := sckcfg.Server{
        Network:  libptc.NetworkUnix,
        Address:  "/tmp/echo.sock",
        PermFile: libprm.New(0666),  // Default: all users
        GroupPerm: -1,  // Default group
    }
    
    // Create and start server
    srv, _ := unix.New(nil, handler, cfg)
    srv.Listen(context.Background())
}
Server with Permissions
import (
    "os/user"
    "strconv"
    // ... other imports
)

func main() {
    // Get group ID for restricted access
    grp, _ := user.LookupGroup("myapp")
    gid, _ := strconv.Atoi(grp.Gid)
    
    // Configure server with restricted permissions
    cfg := sckcfg.Server{
        Network:  libptc.NetworkUnix,
        Address:  "/var/run/myapp.sock",
        PermFile: libprm.New(0660),  // Owner + group only
        GroupPerm: int32(gid),  // Specific group
    }
    
    srv, _ := unix.New(nil, handler, cfg)
    srv.Listen(context.Background())
}
Production Server
func main() {
    // Handler with error handling
    handler := func(ctx libsck.Context) {
        defer ctx.Close()
        
        buf := make([]byte, 4096)
        for ctx.IsConnected() {
            n, err := ctx.Read(buf)
            if err != nil {
                log.Printf("Read error: %v", err)
                return
            }
            
            if _, err := ctx.Write(buf[:n]); err != nil {
                log.Printf("Write error: %v", err)
                return
            }
        }
    }
    
    // Configuration with idle timeout and permissions
    cfg := sckcfg.Server{
        Network:        libptc.NetworkUnix,
        Address:        "/var/run/myapp.sock",
        PermFile:       libprm.New(0660),
        GroupPerm:      -1,
        ConIdleTimeout: 5 * time.Minute,
    }
    
    srv, _ := unix.New(nil, handler, cfg)
    
    // Register monitoring callbacks
    srv.RegisterFuncError(func(errs ...error) {
        for _, err := range errs {
            log.Printf("Server error: %v", err)
        }
    })
    
    srv.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
        log.Printf("[%s] %s -> %s", state, remote, local)
    })
    
    // Start server
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    
    go func() {
        if err := srv.Listen(ctx); err != nil {
            log.Fatalf("Server error: %v", err)
        }
    }()
    
    // Graceful shutdown on signal
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
    
    <-sigChan
    log.Println("Shutting down...")
    
    shutdownCtx, shutdownCancel := context.WithTimeout(
        context.Background(), 30*time.Second)
    defer shutdownCancel()
    
    if err := srv.Shutdown(shutdownCtx); err != nil {
        log.Printf("Shutdown error: %v", err)
    }
    
    log.Println("Server stopped")
}

Best Practices

✅ DO

Always close connections:

handler := func(ctx libsck.Context) {
    defer ctx.Close()  // Ensures cleanup
    // Handler logic...
}

Implement graceful shutdown:

shutdownCtx, cancel := context.WithTimeout(
    context.Background(), 30*time.Second)
defer cancel()

if err := srv.Shutdown(shutdownCtx); err != nil {
    log.Printf("Shutdown timeout: %v", err)
}

Monitor connection count:

go func() {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
    
    for range ticker.C {
        count := srv.OpenConnections()
        if count > 1000 {
            log.Printf("WARNING: High connection count: %d", count)
        }
    }
}()

Handle errors properly:

n, err := ctx.Read(buf)
if err != nil {
    if err != io.EOF {
        log.Printf("Read error: %v", err)
    }
    return  // Exit handler
}
❌ DON'T

Don't ignore graceful shutdown:

// ❌ BAD: Abrupt shutdown loses data
srv.Close()

// ✅ GOOD: Wait for connections to finish
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(ctx)

Don't leak goroutines:

// ❌ BAD: Forgot to close connection
handler := func(ctx libsck.Context) {
    io.Copy(ctx, ctx)  // Connection never closed!
}

// ✅ GOOD: Always defer Close
handler := func(ctx libsck.Context) {
    defer ctx.Close()
    io.Copy(ctx, ctx)
}

Don't use in ultra-high concurrency:

// ❌ BAD: 100K+ connections on goroutine-per-connection
// This will consume excessive memory and goroutines

// ✅ GOOD: For >10K connections, use event-driven model
// Consider alternatives like netpoll, epoll, or io_uring
Testing

The package includes a comprehensive test suite with 74.9% code coverage and 60 test specifications using BDD methodology (Ginkgo v2 + Gomega).

Key test coverage:

  • ✅ All public APIs and lifecycle operations
  • ✅ Concurrent access with race detector (zero races detected)
  • ✅ Performance benchmarks (throughput, latency, scalability)
  • ✅ Error handling and edge cases
  • ✅ File permissions and group ownership
  • ✅ Context integration and cancellation

For detailed test documentation, see TESTING.md.


API Reference

ServerUnix Interface
type ServerUnix interface {
    libsck.Server  // Embedded interface
    
    // Register Unix socket path, permissions, and group
    RegisterSocket(unixFile string, perm libprm.Perm, gid int32) error
}

Inherited from libsck.Server:

// Start accepting connections
Listen(ctx context.Context) error

// Stop accepting, wait for connections to close
Shutdown(ctx context.Context) error

// Stop accepting, close all connections immediately
Close() error

// Check if server is accepting connections
IsRunning() bool

// Check if server is draining connections
IsGone() bool

// Get current connection count
OpenConnections() int64

// Register callbacks
RegisterFuncError(f libsck.FuncError)
RegisterFuncInfo(f libsck.FuncInfo)
RegisterFuncInfoServer(f libsck.FuncInfoSrv)
Configuration
type Server struct {
    Network        libptc.NetworkType  // Protocol (NetworkUnix)
    Address        string              // Socket file path "/tmp/app.sock"
    PermFile       libprm.Perm         // File permissions (e.g., 0600, 0660)
    GroupPerm      int32               // Group ID or -1 for default
    ConIdleTimeout time.Duration       // Idle timeout (0=disabled)
}

Important Notes:

  • Address: Must be an absolute or relative path to socket file
  • PermFile: File permissions for the socket (0600=owner only, 0660=owner+group, 0666=all)
  • GroupPerm: Unix group ID (must be ≤32767) or -1 to use process default group
  • Socket file is automatically created on Listen() and removed on Shutdown()/Close()
Error Codes
var (
    ErrInvalidHandler  = "invalid handler"           // Handler function is nil
    ErrInvalidInstance = "invalid socket instance"   // Internal server error
    ErrInvalidGroup    = "group gid exceed MaxGID"   // Group ID > 32767
    ErrServerClosed    = "server closed"             // Server already closed
    ErrContextClosed   = "context closed"            // Context cancelled
    ErrShutdownTimeout = "timeout on stopping socket"  // Shutdown timeout
    ErrGoneTimeout     = "timeout on closing connections" // Close timeout
    ErrIdleTimeout     = "timeout on idle connections"    // Connection idle
)

Platform Limitations:

  • MaxGID = 32767 (maximum Unix group ID on Linux)
  • Socket path length typically limited to 108 characters on Linux

Contributing

Contributions are welcome! Please follow these guidelines:

Reporting Bugs

If you find a bug, please open an issue on GitHub with:

  1. Description: Clear and concise description of the bug
  2. Reproduction Steps: Minimal code example to reproduce the issue
  3. Expected Behavior: What you expected to happen
  4. Actual Behavior: What actually happened
  5. Environment: Go version, OS, and relevant system information
  6. Logs/Errors: Any error messages or stack traces

Submit issues at: https://github.com/nabbar/golib/issues

Code Contributions
  1. Code Quality

    • Follow Go best practices and idioms
    • Maintain or improve code coverage (target: >80%)
    • Pass all tests including race detector
    • Use gofmt and golint
  2. AI Usage Policy

    • AI must NEVER be used to generate package code or core functionality
    • AI assistance is limited to:
      • Testing (writing and improving tests)
      • Debugging (troubleshooting and bug resolution)
      • Documentation (comments, README, TESTING.md)
    • All AI-assisted work must be reviewed and validated by humans
  3. Testing

    • Add tests for new features
    • Use Ginkgo v2 / Gomega for test framework
    • Ensure zero race conditions with go test -race
  4. Documentation

    • Update GoDoc comments for public APIs
    • Add examples for new features
    • Update README.md and TESTING.md if needed

Improvements & Security

Current Status

The package is production-ready with no urgent improvements or security vulnerabilities identified.

Code Quality Metrics
  • 74.9% test coverage (target: >80%)
  • Zero race conditions detected with -race flag
  • Thread-safe implementation using atomic operations
  • File permissions security with chmod/chown support
  • Graceful shutdown with connection draining
  • Platform-specific (Linux and macOS only)
Known Limitations

Architectural Constraints:

  1. Scalability: Goroutine-per-connection model is optimal for 1-10K connections. For >10K connections, consider event-driven alternatives (epoll, io_uring)
  2. No Protocol Framing: Applications must implement their own message framing layer
  3. No Connection Pooling: Each connection is independent - implement pooling at application level if needed
  4. No Built-in Rate Limiting: Application must implement rate limiting for connection/request throttling
  5. No Metrics Export: No built-in Prometheus or OpenTelemetry integration - use callbacks for custom metrics
  6. Platform Limitation: Linux and macOS only (not Windows - Windows has named pipes instead)

Not Suitable For:

  • Ultra-high concurrency scenarios (>50K simultaneous connections)
  • Low-latency high-frequency trading (<10µs response time requirements)
  • Short-lived connections at extreme rates (>100K connections/second)
  • Remote/network communication (use TCP/gRPC instead)
  • Windows platforms (use named pipes or TCP loopback)
Future Enhancements (Non-urgent)

The following enhancements could be considered for future versions:

  1. Connection Pooling: Built-in connection pool management for backend proxies
  2. Rate Limiting: Configurable per-IP and global rate limiting
  3. Metrics Integration: Optional Prometheus/OpenTelemetry exporters
  4. Protocol Helpers: Common framing protocols (length-prefixed, delimited, chunked)
  5. Load Balancing: Built-in connection distribution strategies
  6. Circuit Breaker: Automatic failure detection and recovery

These are optional improvements and not required for production use. The current implementation is stable and performant for its intended use cases.

Security Considerations

Security Best Practices Applied:

  • TLS 1.2/1.3 with configurable cipher suites
  • Mutual TLS (mTLS) support for client authentication
  • Idle timeout to prevent resource exhaustion
  • Graceful shutdown prevents data loss
  • Context cancellation for timeouts and deadlines

No Known Vulnerabilities:

  • Regular security audits performed
  • Dependencies limited to Go stdlib and internal golib packages
  • No CVEs reported
Comparison with Alternatives
Feature unix (this package) TCP Loopback Named Pipes (Windows)
Protocol Unix domain socket TCP/IP Windows IPC
Overhead Minimal (no network stack) TCP/IP stack Minimal
Throughput Very High (~1M req/s) High (~500K req/s) High
Latency Very Low (<500µs) Low (1-2ms) Low
Security Filesystem permissions Firewall rules ACLs
Platform Linux/macOS All platforms Windows only
Best For Local IPC, same-host Network compatibility Windows IPC
Learning Curve Low Low Medium

Resources

Package Documentation
  • GoDoc - Complete API reference with function signatures, method descriptions, and runnable examples. Essential for understanding the public interface and usage patterns.

  • doc.go - In-depth package documentation including design philosophy, architecture diagrams, performance considerations, and security best practices. Provides detailed explanations of internal mechanisms and Unix socket-specific features.

  • TESTING.md - Comprehensive test suite documentation covering test architecture, BDD methodology with Ginkgo v2, coverage analysis (74.9%), performance benchmarks, and guidelines for writing new tests. Includes troubleshooting and CI integration examples.

External References

AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for testing, documentation, and bug resolution under human supervision. All core functionality is human-designed and validated.


License

MIT License - See LICENSE file for details.

Copyright (c) 2022 Nicolas JUHEL


Maintained by: Nicolas JUHEL
Package: github.com/nabbar/golib/socket/server/unix
Version: See releases for versioning

Documentation

Overview

Package unix provides a high-performance, production-ready Unix domain socket server implementation. It is specifically optimized for local Inter-Process Communication (IPC) on POSIX-compliant systems (Linux and macOS).

1. ARCHITECTURE

This package implements a connection-oriented (SOCK_STREAM) Unix domain socket server. Unlike TCP, which is designed for network-wide communication, Unix sockets are optimized for communication between processes on the same host. They reside in the filesystem and benefit from kernel-level optimizations that bypass the network stack entirely.

┌───────────────────────────────────────────────────────────────────────────────┐
│                           UNIX SOCKET SERVER (srv)                            │
├───────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│   [ FILESYSTEM LAYER ]      [ KERNEL SPACE ]          [ USER SPACE ]          │
│          │                         │                        │                 │
│   Socket File (Path) <─── [ Accept Queue ]  <─── [ Accept Loop (srv) ]        │
│          │                         │                        │                 │
│          │                         │                        ▼                 │
│   [ RESOURCE MANAGEMENT ]                             [ CONNECTION HANDLER ]  │
│          │                                                      │             │
│   ┌──────────────┐          ┌────────────────┐          ┌───────────────┐     │
│   │  sync.Pool   │ <─────── │ Context Reset  │ <─────── │  net.Conn     │     │
│   │ (sCtx items) │          └────────────────┘          │  (Raw Socket) │     │
│   └──────────────┘                                      └───────┬───────┘     │
│          ^                                                      │             │
│          │                                                      v             │
│   ┌──────────────┐          ┌────────────────┐          ┌───────────────┐     │
│   │ Idle Manager │ <─────── │ Registration   │ <─────── │ User Handler  │     │
│   │ (sckidl.Mgr) │          └────────────────┘          │ (Goroutine)   │     │
│   └──────────────┘                                      └───────────────┘     │
│                                                                 │             │
│   [ SHUTDOWN CONTROL ]                                          v             │
│   ┌──────────────────┐                                  ┌───────────────┐     │
│   │   Gone Channel   │ ───────(Broadcast)─────────────> │   Cleanup     │     │
│   │      (gnc)       │                                  │ (Pool Return) │     │
│   └──────────────────┘                                  └───────────────┘     │
└───────────────────────────────────────────────────────────────────────────────┘

2. PERFORMANCE OPTIMIZATIONS

The server has been engineered to handle extreme loads with minimal overhead. Several key patterns are employed to achieve this:

  • Zero-Allocation Connection Path (sync.Pool): Connection contexts (sCtx) are managed via sync.Pool. This reduces allocations on the critical path to nearly zero, preventing memory fragmentation and reducing GC pause times.

  • Centralized Idle Management: Integration with a centralized sckidl.Manager avoids per-connection timers, reducing CPU consumption by approximately 18% under heavy load.

  • Broadcast Shutdown Signaling (gnc Channel): Uses a dedicated 'gone' channel (gnc) to signal immediate termination to all active connection goroutines, ensuring instant reaction to server shutdown requests.

3. DATA FLOW

The following diagram illustrates the lifecycle of a Unix domain socket connection:

[CLIENT]            [FILESYSTEM]          [SERVER LISTENER]          [IDLE MGR]          [HANDLER]
   │                     │                       │                       │                   │
   │──(Connect/Path)────>│                       │                       │                   │
   │                     │──────(Inodes)────────>│                       │                   │
   │                     │                       │                       │                   │
   │                     │                       │───(Accept net.Conn)──>│                   │
   │                     │                       │                       │                   │
   │                     │                       │───(Fetch sCtx)───────>│                   │
   │                     │                       │                       │                   │
   │                     │                       │───(Register)─────────>│                   │
   │                     │                       │                       │                   │
   │                     │                       │───(Spawn Handler)────────────────────────>│
   │                     │                       │                       │                   │
   │<──(I/O Activity)────┼───────────────────────┼───────────────────────┼───────────────────│
   │                     │                       │                       │                   │
   │                     │                       │                       │<──(Atomic Reset)──│
   │                     │                       │                       │                   │
   │───(Close)──────────>│                       │                       │                   │
   │                     │──────(Cleanup)───────>│                       │                   │
   │                     │                       │───(Unregister)───────>│                   │
   │                     │                       │                       │                   │
   │                     │                       │───(Release sCtx)─────>│                   │
   │                     │                       │                       │                   │

4. SECURITY & ISOLATION

  • Filesystem Permissions: Control access via standard chmod (e.g., 0600 for owner-only).
  • Group Control: Assign socket ownership to a specific GID for cross-process communication.
  • No Network Exposure: The socket is not reachable from other hosts, reducing the attack surface.

5. BEST PRACTICES & USE CASES

  • Use Case: High-performance communication with sidecar containers.
  • Use Case: Local database connections (e.g., PostgreSQL, Redis).
  • Best Practice: Always use defer srv.Close() to ensure the socket file is removed from the filesystem.
  • Best Practice: Use UpdateConn to set socket buffer sizes for high-bandwidth connections.

6. IMPLEMENTATION EXAMPLE

package main

import (
    "context"
    "log"
    "github.com/nabbar/golib/file/perm"
    "github.com/nabbar/golib/socket"
    "github.com/nabbar/golib/socket/config"
    "github.com/nabbar/golib/socket/server/unix"
)

func main() {
    // 1. Define the handler
    handler := func(ctx socket.Context) {
        defer ctx.Close()
        buf := make([]byte, 1024)
        n, err := ctx.Read(buf)
        if err != nil {
            log.Printf("Error reading from Unix socket: %v", err)
            return
        }
        log.Printf("Received %d bytes: %s", n, string(buf[:n]))
        _, err = ctx.Write([]byte("ACK from Unix Server"))
        if err != nil {
            log.Printf("Error writing to Unix socket: %v", err)
        }
    }

    // 2. Setup Config
    cfg := config.Server{
        Network:   "unix",
        Address:   "/tmp/app.sock",
        PermFile:  perm.NewPerm(0660), // Read/Write for owner and group
        GroupPerm: -1,                 // Use default group of the process
    }

    // 3. Instantiate
    srv, err := unix.New(nil, handler, cfg)
    if err != nil {
        log.Fatalf("Failed to create Unix server: %v", err)
    }

    // 4. Start
    log.Printf("Starting Unix server on %s", cfg.Address)
    if err := srv.Listen(context.Background()); err != nil {
        log.Fatalf("Unix server failed to listen: %v", err)
    }
}

Package unix provides error definitions for the Unix domain socket server.

These errors are used to signal issues with configuration, lifecycle management, and platform-specific constraints.

Example

Example demonstrates a basic echo server. This is the simplest possible Unix socket server implementation.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"time"

	libprm "github.com/nabbar/golib/file/perm"

	libptc "github.com/nabbar/golib/network/protocol"

	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksru "github.com/nabbar/golib/socket/server/unix"
)

func main() {
	// Create handler function that echoes back received data
	handler := func(c libsck.Context) {
		defer func() { _ = c.Close() }()
		buf := make([]byte, 1024)
		for {
			n, err := c.Read(buf)
			if err != nil {
				return
			}
			if n > 0 {
				_, _ = c.Write(buf[:n])
			}
		}
	}

	// Create temporary socket file
	tmpDir := os.TempDir()
	socketPath := filepath.Join(tmpDir, fmt.Sprintf("example-%d.sock", time.Now().UnixNano()))
	defer os.Remove(socketPath)

	// Create server configuration
	cfg := sckcfg.Server{
		Network:   libptc.NetworkUnix,
		Address:   socketPath,
		PermFile:  libprm.Perm(0600),
		GroupPerm: -1,
	}

	// Create server
	srv, err := scksru.New(nil, handler, cfg)
	if err != nil {
		panic(err)
	}

	// Start server
	ctx := context.Background()
	go func() {
		_ = srv.Listen(ctx)
	}()

	// Wait for server to start
	time.Sleep(100 * time.Millisecond)

	// Shutdown after demonstration
	_ = srv.Shutdown(ctx)
}
Example (Complete)

Example_complete demonstrates a production-ready server with all features. This example shows error handling, monitoring, graceful shutdown, and logging.

package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"time"

	libdur "github.com/nabbar/golib/duration"
	libprm "github.com/nabbar/golib/file/perm"

	libptc "github.com/nabbar/golib/network/protocol"

	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksru "github.com/nabbar/golib/socket/server/unix"
)

func main() {
	// Handler with proper error handling
	handler := func(c libsck.Context) {
		defer func() { _ = c.Close() }()

		buf := make([]byte, 4096)
		for c.IsConnected() {
			n, err := c.Read(buf)
			if err != nil {
				return
			}

			if n > 0 {
				if _, err := c.Write(buf[:n]); err != nil {
					return
				}
			}
		}
	}

	// Create temporary socket file
	tmpDir := os.TempDir()
	socketPath := filepath.Join(tmpDir, fmt.Sprintf("complete-%d.sock", time.Now().UnixNano()))
	defer os.Remove(socketPath)

	// Create configuration with idle timeout
	cfg := sckcfg.Server{
		Network:        libptc.NetworkUnix,
		Address:        socketPath,
		PermFile:       libprm.Perm(0660),
		GroupPerm:      -1,
		ConIdleTimeout: libdur.Minutes(5),
	}

	// Create server
	srv, err := scksru.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Failed to create server: %v\n", err)
		return
	}

	// Register monitoring callbacks
	srv.RegisterFuncError(func(errs ...error) {
		for _, e := range errs {
			fmt.Printf("Server error: %v\n", e)
		}
	})

	srv.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
		fmt.Printf("Connection %s from %s\n", state, remote)
	})

	// Start server
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		if err := srv.Listen(ctx); err != nil {
			fmt.Printf("Server stopped: %v\n", err)
		}
	}()

	// Wait for server to be ready
	time.Sleep(50 * time.Millisecond)
	fmt.Printf("Server running with %d connections\n", srv.OpenConnections())

	// Graceful shutdown
	shutdownCtx, shutdownCancel := context.WithTimeout(
		context.Background(), 10*time.Second)
	defer shutdownCancel()

	if err := srv.Shutdown(shutdownCtx); err != nil {
		fmt.Printf("Shutdown error: %v\n", err)
	}

	fmt.Println("Server stopped gracefully")
}
Output:
Server running with 0 connections
Server stopped gracefully

Index

Examples

Constants

View Source
const MaxGID = 32767

MaxGID defines the maximum allowed Unix group ID value (32767). This limit is common on standard 16-bit Unix systems and is used here for safety during file ownership operations (`os.Chown`).

Variables

View Source
var (
	// ErrInvalidUnixFile is returned when the provided Unix socket path is empty or malformed.
	// This error occurs during the `RegisterSocket` or `Listen` phase if the path is not a
	// valid filesystem path or does not satisfy the requirements for a Unix socket file.
	//
	// # Usage Case:
	//   - An empty string is passed as the `unixFile` parameter in `RegisterSocket`.
	//   - The path points to a directory instead of a file.
	ErrInvalidUnixFile = fmt.Errorf("invalid unix file for socket listening")

	// ErrInvalidGroup is returned when the specified Group ID (GID) exceeds the maximum allowed value (32767).
	// Unix group IDs must be within the range supported by the operating system to ensure
	// proper file permission management via `os.Chown`.
	//
	// # Usage Case:
	//   - A GID greater than 32767 is passed to `RegisterSocket` on a system with standard 16-bit GID limits.
	ErrInvalidGroup = fmt.Errorf("invalid unix group for socket group permission")

	// ErrInvalidHandler is returned when attempting to start a server without a valid handler function.
	// A `HandlerFunc` is required by the `New()` constructor and must be provided to process
	// incoming client connections.
	//
	// # Usage Case:
	//   - The `hdl` parameter in `New()` is nil.
	//   - The server's `hdl` field has been zeroed out before calling `Listen`.
	ErrInvalidHandler = fmt.Errorf("invalid handler")

	// ErrShutdownTimeout is returned when the server shutdown process exceeds the timeout
	// specified in the context passed to the `Shutdown()` method.
	//
	// # Usage Case:
	//   - Active connections are not closing within the 25-second (or custom) timeout period during a graceful shutdown.
	//   - The server is under heavy load and cannot finish draining connections before the deadline.
	ErrShutdownTimeout = fmt.Errorf("timeout on stopping socket")

	// ErrInvalidInstance is returned when an operation is performed on a nil server instance.
	// This check is used in methods like `Shutdown` to prevent potential nil-pointer panics.
	//
	// # Usage Case:
	//   - Calling `srv.Shutdown(ctx)` when `srv` is nil.
	ErrInvalidInstance = fmt.Errorf("invalid socket instance")
)

Functions

This section is empty.

Types

type ServerUnix

type ServerUnix interface {
	libsck.Server

	// RegisterSocket configures the metadata for the Unix socket file.
	//
	// # Important:
	// This method MUST be called before `Listen()`.
	//
	// Parameters:
	//   - unixFile: The filesystem path for the socket file (e.g., "/var/run/app.sock").
	//   - perm: The permissions to apply to the socket file (e.g., 0600, 0660).
	//   - gid: The Group ID to assign to the socket file, or -1 to use the default group.
	//
	// Returns:
	//   - ErrInvalidUnixFile: If the path is empty or invalid.
	//   - ErrInvalidGroup: If the GID exceeds MaxGID.
	//
	// # Behavior:
	//   - The socket file is created only when `Listen()` is called.
	//   - Existing files at the same path are automatically removed during startup.
	//   - The socket file is removed from the filesystem during shutdown.
	RegisterSocket(unixFile string, perm libprm.Perm, gid int32) error
}

ServerUnix defines the comprehensive interface for a high-performance Unix domain socket server. It extends the base `github.com/nabbar/golib/socket.Server` interface with Unix-specific functionality, such as socket file permission and ownership management.

Server Operation:

  • Mode: Connection-oriented (SOCK_STREAM).
  • Transport: Unix domain sockets (AF_UNIX), communicating via local files.
  • Concurrency: Goroutine-per-connection model with optimized resource pooling.
  • Lifecycle Management: Atomic state tracking for running/shutdown phases.

Key Responsibilities:

  • Socket Creation: Handles the creation, validation, and cleanup of the Unix socket file.
  • Permissions: Manages file access control via `chmod` and `chown`.
  • Resource Optimization: Recycles connection contexts (`sCtx`) to reduce GC overhead.
  • Idle Control: Integrates with a centralized Idle Manager for efficient timeout scanning.

Lifecycle Methods Inherited from libsck.Server:

  • Listen(context.Context) error: Starts the accept loop. Blocks until shutdown or error.
  • Shutdown(context.Context) error: Initiates a graceful shutdown with draining.
  • Close() error: Triggers an immediate shutdown.
  • IsRunning() bool: Returns true if the server is accepting connections.
  • IsGone() bool: Returns true if the server has finished its shutdown cycle.
  • OpenConnections() int64: Returns the number of currently active connections.

# Thread Safety: All methods are safe for concurrent use by multiple goroutines. Internal synchronization is achieved via lock-free atomic operations and a `sync.Pool`.

Example (ContextValues)

ExampleServerUnix_contextValues demonstrates using context values

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"time"

	libprm "github.com/nabbar/golib/file/perm"

	libptc "github.com/nabbar/golib/network/protocol"

	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksru "github.com/nabbar/golib/socket/server/unix"
)

func main() {
	type contextKey string
	const requestIDKey contextKey = "requestID"

	handler := func(c libsck.Context) {
		defer func() { _ = c.Close() }()

		// Access context value
		if requestID := c.Value(requestIDKey); requestID != nil {
			fmt.Printf("Processing request ID: %v\n", requestID)
		}
	}

	tmpDir := os.TempDir()
	socketPath := filepath.Join(tmpDir, fmt.Sprintf("context-%d.sock", time.Now().UnixNano()))
	defer os.Remove(socketPath)

	cfg := sckcfg.Server{
		Network:   libptc.NetworkUnix,
		Address:   socketPath,
		PermFile:  libprm.Perm(0600),
		GroupPerm: -1,
	}

	srv, _ := scksru.New(nil, handler, cfg)
	fmt.Println("Server with context values ready")
	_ = srv.Shutdown(context.Background())
}
Output:
Server with context values ready
Example (IdleTimeout)

ExampleServerUnix_idleTimeout demonstrates idle connection timeout

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"time"

	libdur "github.com/nabbar/golib/duration"
	libprm "github.com/nabbar/golib/file/perm"

	libptc "github.com/nabbar/golib/network/protocol"

	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksru "github.com/nabbar/golib/socket/server/unix"
)

func main() {
	handler := func(c libsck.Context) {
		defer func() { _ = c.Close() }()
		// Handler that doesn't read/write (connection will idle)
		time.Sleep(200 * time.Millisecond)
	}

	tmpDir := os.TempDir()
	socketPath := filepath.Join(tmpDir, fmt.Sprintf("idle-%d.sock", time.Now().UnixNano()))
	defer os.Remove(socketPath)

	cfg := sckcfg.Server{
		Network:        libptc.NetworkUnix,
		Address:        socketPath,
		PermFile:       libprm.Perm(0600),
		GroupPerm:      -1,
		ConIdleTimeout: libdur.ParseDuration(100 * time.Millisecond),
	}
	srv, _ := scksru.New(nil, handler, cfg)

	ctx := context.Background()
	go func() {
		_ = srv.Listen(ctx)
	}()

	time.Sleep(50 * time.Millisecond)
	fmt.Println("Server with idle timeout running")

	_ = srv.Shutdown(ctx)
}
Output:
Server with idle timeout running
Example (Monitoring)

ExampleServerUnix_monitoring demonstrates complete monitoring setup

package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"time"

	libprm "github.com/nabbar/golib/file/perm"

	libptc "github.com/nabbar/golib/network/protocol"

	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksru "github.com/nabbar/golib/socket/server/unix"
)

func main() {
	handler := func(c libsck.Context) {
		defer func() { _ = c.Close() }()
	}

	tmpDir := os.TempDir()
	socketPath := filepath.Join(tmpDir, fmt.Sprintf("monitor-%d.sock", time.Now().UnixNano()))
	defer os.Remove(socketPath)

	cfg := sckcfg.Server{
		Network:   libptc.NetworkUnix,
		Address:   socketPath,
		PermFile:  libprm.Perm(0600),
		GroupPerm: -1,
	}
	srv, _ := scksru.New(nil, handler, cfg)

	// Register all callbacks
	srv.RegisterFuncError(func(errs ...error) {
		fmt.Println("Error callback registered")
	})

	srv.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
		fmt.Println("Connection callback registered")
	})

	srv.RegisterFuncInfoServer(func(msg string) {
		fmt.Println("Server info callback registered")
	})

	fmt.Println("All monitoring callbacks configured")
	_ = srv.Shutdown(context.Background())
}
Output:
All monitoring callbacks configured

func New

New creates and initializes a new Unix domain socket server instance. It sets up the internal context pool, configuration, and monitoring callbacks.

Initialization Details:

  • Callback Hooks: Initializes default, non-blocking callbacks for error and info reporting.
  • Context Pool: Creates a `sync.Pool` to recycle `sCtx` structures, achieving zero-allocation connections.
  • Idle Manager: If `cfg.ConIdleTimeout` is set, initializes a centralized `sckidl.Manager` to perform efficient periodic scans of inactive connections.
  • Gone Channel: Initializes the first signaling channel for the server lifecycle.

Performance Considerations:

  • Under high load, the use of `sync.Pool` drastically reduces GC pressure compared to allocating a new context for every connection.
  • The centralized Idle Manager is more efficient than individual timers for thousands of connections.

Parameters:

  • upd: Optional callback for per-connection tuning (e.g., setting buffer sizes).
  • hdl: Required callback that implements the server's business logic for each connection.
  • cfg: Server configuration structure containing address, timeout, and permission settings.

Returns:

  • ServerUnix: The initialized server instance.
  • error: If the configuration is invalid or if the handler is nil.

Example:

handler := func(r io.Reader, w io.Writer) {
    _, _ = io.WriteString(w, "Hello from Unix Server!\n")
}

srv, _ := unix.New(nil, handler, config.Server{
    Address: "/tmp/my.sock",
    PermFile: perm.New(0666),
})
_ = srv.Listen(context.Background())
Example

ExampleNew demonstrates creating a Unix socket server

package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"time"

	libprm "github.com/nabbar/golib/file/perm"

	libptc "github.com/nabbar/golib/network/protocol"

	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksru "github.com/nabbar/golib/socket/server/unix"
)

func main() {
	// Define connection handler
	handler := func(c libsck.Context) {
		defer func() { _ = c.Close() }()
		_, _ = io.Copy(c, c) // Echo
	}

	// Create temporary socket file
	tmpDir := os.TempDir()
	socketPath := filepath.Join(tmpDir, fmt.Sprintf("new-%d.sock", time.Now().UnixNano()))
	defer os.Remove(socketPath)

	// Create configuration
	cfg := sckcfg.Server{
		Network:   libptc.NetworkUnix,
		Address:   socketPath,
		PermFile:  libprm.Perm(0600),
		GroupPerm: -1,
	}

	// Create server
	srv, err := scksru.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Failed to create server: %v\n", err)
		return
	}

	fmt.Printf("Server created successfully\n")
	_ = srv
}
Output:
Server created successfully
Example (Permissions)

ExampleNew_permissions demonstrates file permission configuration

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"time"

	libprm "github.com/nabbar/golib/file/perm"

	libptc "github.com/nabbar/golib/network/protocol"

	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksru "github.com/nabbar/golib/socket/server/unix"
)

func main() {
	handler := func(c libsck.Context) {
		defer func() { _ = c.Close() }()
	}

	tmpDir := os.TempDir()
	socketPath := filepath.Join(tmpDir, fmt.Sprintf("perms-%d.sock", time.Now().UnixNano()))
	defer os.Remove(socketPath)

	// Configure different permission modes
	cfg := sckcfg.Server{
		Network:   libptc.NetworkUnix,
		Address:   socketPath,
		PermFile:  libprm.Perm(0600), // Owner only
		GroupPerm: -1,                // Default group
	}

	srv, err := scksru.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Server with custom permissions created")
	_ = srv.Shutdown(context.Background())
}
Output:
Server with custom permissions created
Example (WithUpdateConn)

ExampleNew_withUpdateConn demonstrates custom connection configuration

package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"time"

	libprm "github.com/nabbar/golib/file/perm"

	libptc "github.com/nabbar/golib/network/protocol"

	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksru "github.com/nabbar/golib/socket/server/unix"
)

func main() {
	// UpdateConn callback to configure socket options
	upd := func(c net.Conn) {
		if unixConn, ok := c.(*net.UnixConn); ok {
			_ = unixConn.SetReadBuffer(8192)
			_ = unixConn.SetWriteBuffer(8192)
		}
	}

	handler := func(c libsck.Context) {
		defer func() { _ = c.Close() }()
	}

	tmpDir := os.TempDir()
	socketPath := filepath.Join(tmpDir, fmt.Sprintf("update-%d.sock", time.Now().UnixNano()))
	defer os.Remove(socketPath)

	cfg := sckcfg.Server{
		Network:   libptc.NetworkUnix,
		Address:   socketPath,
		PermFile:  libprm.Perm(0600),
		GroupPerm: -1,
	}

	srv, err := scksru.New(upd, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Server with custom connection config created")
	_ = srv.Shutdown(context.Background())
}
Output:
Server with custom connection config created

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL