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)
}
Output:
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 ¶
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 ¶
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 ¶
func New(upd libsck.UpdateConn, hdl libsck.HandlerFunc, cfg sckcfg.Server) (ServerUnix, error)
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