Search documentation of snmpkit

# SnmpKit.SnmpLib.Pool (snmpkit v1.2.0)

Connection pooling and session management for high-performance SNMP operations.

This module provides sophisticated connection pooling capabilities designed for applications that need to manage many concurrent SNMP operations efficiently. Based on patterns proven in the DDumb project for handling 100+ concurrent device polls.

## Features

- Connection Pooling: Efficient reuse of UDP sockets across operations
- Session Management: Per-device session tracking with state management
- Load Balancing: Intelligent distribution of operations across pool workers
- Health Monitoring: Automatic detection and handling of unhealthy connections
- Performance Metrics: Built-in monitoring and performance tracking
- Graceful Degradation: Circuit breaker patterns for failing devices

## Pool Strategies

### FIFO Pool (Default)

Best for general-purpose SNMP operations with mixed device types.

### Round-Robin Pool

Optimal for polling multiple devices with similar characteristics.

### Device-Affinity Pool

Routes operations for the same device to the same worker for session consistency.

## Usage Patterns

    # Start a pool for network monitoring
    {:ok, pool_pid} = SnmpKit.SnmpLib.Pool.start_pool(:network_monitor,
      strategy: :device_affinity,
      size: 20,
      max_overflow: 10
    )

    # Perform operations through the pool
    SnmpKit.SnmpLib.Pool.with_connection(:network_monitor, "192.168.1.1", fn conn ->
      SnmpKit.SnmpLib.Manager.get_multi(conn.socket, "192.168.1.1", oids, conn.opts)
    end)

    # Get pool statistics
    stats = SnmpKit.SnmpLib.Pool.get_stats(:network_monitor)
    IO.inspect(stats.active_connections)

## Performance Benefits

- 60-80% reduction in socket creation overhead
- Improved throughput for high-frequency polling
- Lower memory usage through connection reuse
- Better resource utilization with intelligent load balancing

# Summary

## Types

connection()
pool_name()
pool_opts()
pool_stats()
pool_strategy()

## Functions

checkin_connection(pool_name, connection)

Returns a connection to the pool after manual checkout.

checkout_connection(pool_name, device, opts \\ [])

Checks out a connection from the pool for manual management.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

cleanup_unhealthy(pool_name)

Removes unhealthy connections from the pool and replaces them.

get_stats(pool_name)

Gets comprehensive statistics about the pool's performance and health.

health_check(pool_name)

Forces a health check on all connections in the pool.

start_pool(pool_name, opts \\ [])

Starts a new connection pool with the specified configuration.

stop_pool(pool_name, timeout \\ 5000)

Stops a connection pool and cleans up all resources.

with_connection(pool_name, device, fun, opts \\ [])

Executes a function with a pooled connection, handling checkout/checkin automatically.

# Types

# connection()

    @type connection() :: %{
      socket: :gen_udp.socket(),
      pid: pid(),
      device: binary() | nil,
      last_used: integer(),
      health_status: :healthy | :degraded | :unhealthy,
      operation_count: non_neg_integer(),
      error_count: non_neg_integer()
    }

# pool_name()

    @type pool_name() :: atom()

# pool_opts()

    @type pool_opts() :: [
      strategy: pool_strategy(),
      size: pos_integer(),
      max_overflow: non_neg_integer(),
      checkout_timeout: pos_integer(),
      max_idle_time: pos_integer(),
      health_check_interval: pos_integer(),
      worker_opts: keyword()
    ]

# pool_stats()

    @type pool_stats() :: %{
      name: pool_name(),
      strategy: pool_strategy(),
      size: pos_integer(),
      active_connections: non_neg_integer(),
      idle_connections: non_neg_integer(),
      overflow_connections: non_neg_integer(),
      total_checkouts: non_neg_integer(),
      total_checkins: non_neg_integer(),
      health_status: map(),
      average_response_time: float()
    }

# pool_strategy()

    @type pool_strategy() :: :fifo | :round_robin | :device_affinity

# Functions

# checkin_connection(pool_name, connection)

    @spec checkin_connection(pool_name(), connection()) :: :ok

Returns a connection to the pool after manual checkout.

## Examples

    :ok = SnmpKit.SnmpLib.Pool.checkin_connection(:snmp_pool, connection)

# checkout_connection(pool_name, device, opts \\ [])

    @spec checkout_connection(pool_name(), binary(), keyword()) ::
      {:ok, connection()} | {:error, any()}

Checks out a connection from the pool for manual management.

Use this when you need more control over connection lifecycle. You must call `checkin_connection/2` when done.

## Examples

    {:ok, conn} = SnmpKit.SnmpLib.Pool.checkout_connection(:snmp_pool, "192.168.1.1")
    # ... perform operations with conn
    :ok = SnmpKit.SnmpLib.Pool.checkin_connection(:snmp_pool, conn)

# child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# cleanup_unhealthy(pool_name)

    @spec cleanup_unhealthy(pool_name()) :: :ok

Removes unhealthy connections from the pool and replaces them.

## Examples

    :ok = SnmpKit.SnmpLib.Pool.cleanup_unhealthy(:snmp_pool)

# get_stats(pool_name)

    @spec get_stats(pool_name()) :: pool_stats()

Gets comprehensive statistics about the pool's performance and health.

## Returns

A map containing:

- Connection counts (active, idle, overflow)
- Operation statistics (checkouts, checkins, response times)
- Health status for connections
- Performance metrics

## Examples

    stats = SnmpKit.SnmpLib.Pool.get_stats(:snmp_pool)
    IO.puts("Active connections: " <> to_string(stats.active_connections))
    IO.puts("Average response time: " <> to_string(stats.average_response_time) <> "ms")

# health_check(pool_name)

    @spec health_check(pool_name()) :: :ok

Forces a health check on all connections in the pool.

Useful for proactive monitoring and debugging connection issues.

## Examples

    :ok = SnmpKit.SnmpLib.Pool.health_check(:snmp_pool)

# start_pool(pool_name, opts \\ [])

    @spec start_pool(pool_name(), pool_opts()) :: {:ok, pid()} | {:error, any()}

Starts a new connection pool with the specified configuration.

## Parameters

- `pool_name`: Unique atom identifier for the pool
- `opts`: Pool configuration options

## Options

- `strategy`: Pool strategy (:fifo, :round_robin, :device_affinity)
- `size`: Base number of connections in the pool (default: 10)
- `max_overflow`: Maximum overflow connections allowed (default: 5)
- `checkout_timeout`: Maximum time to wait for connection (default: 5000ms)
- `max_idle_time`: Maximum idle time before connection cleanup (default: 300000ms)
- `health_check_interval`: Interval for health checks (default: 30000ms)
- `worker_opts`: Options passed to individual workers

## Returns

- `{:ok, pid}`: Pool started successfully
- `{:error, reason}`: Failed to start pool

## Examples

    # Basic pool for general SNMP operations
    {:ok, _pid} = SnmpKit.SnmpLib.Pool.start_pool(:snmp_pool)

    # High-performance pool for device monitoring
    {:ok, _pid} = SnmpKit.SnmpLib.Pool.start_pool(:monitor_pool,
      strategy: :device_affinity,
      size: 25,
      max_overflow: 15,
      health_check_interval: 60_000
    )

# stop_pool(pool_name, timeout \\ 5000)

    @spec stop_pool(pool_name(), pos_integer()) :: :ok

Stops a connection pool and cleans up all resources.

## Parameters

- `pool_name`: Name of the pool to stop
- `timeout`: Maximum time to wait for graceful shutdown (default: 5000ms)

## Examples

    :ok = SnmpKit.SnmpLib.Pool.stop_pool(:snmp_pool)
    :ok = SnmpKit.SnmpLib.Pool.stop_pool(:monitor_pool, 10_000)

# with_connection(pool_name, device, fun, opts \\ [])

    @spec with_connection(pool_name(), binary(), function(), keyword()) :: any()

Executes a function with a pooled connection, handling checkout/checkin automatically.

This is the primary interface for using pooled connections. The function automatically handles connection lifecycle and error recovery.

## Parameters

- `pool_name`: Name of the pool to use
- `device`: Target device (used for device-affinity strategy)
- `fun`: Function to execute with the connection
- `opts`: Additional options for the operation

## Returns

The result of executing the function, or `{:error, reason}` if connection checkout fails or the function raises an exception.

## Examples

    # Perform multiple operations with connection reuse
    result = SnmpKit.SnmpLib.Pool.with_connection(:snmp_pool, "192.168.1.1", fn conn ->
      {:ok, sys_desc} = SnmpKit.SnmpLib.Manager.get_with_socket(conn.socket, "192.168.1.1",
                                                         [1,3,6,1,2,1,1,1,0], conn.opts)
      {:ok, sys_name} = SnmpKit.SnmpLib.Manager.get_with_socket(conn.socket, "192.168.1.1",
                                                         [1,3,6,1,2,1,1,5,0], conn.opts)
      {:sys_desc, sys_desc, :sys_name, sys_name}
    end)