The Apache IoTDB C# client library provides comprehensive exception handling and health monitoring capabilities for SessionPool operations. This document explains how to handle pool depletion scenarios, monitor pool health, and implement recovery strategies.
SessionPoolDepletedException is a specialized exception thrown when the SessionPool cannot provide a client connection. This indicates that:
The exception provides detailed diagnostic information through the following properties:
| Property | Type | Description |
|---|---|---|
DepletionReason | string | A human-readable description of why the pool was depleted |
AvailableClients | int | Number of currently available clients in the pool at the time of exception |
TotalPoolSize | int | The total configured size of the session pool |
FailedReconnections | int | Number of failed reconnection attempts since the pool was opened |
using Apache.IoTDB; using System; try { var sessionPool = new SessionPool.Builder() .SetHost("127.0.0.1") .SetPort(6667) .SetPoolSize(4) .Build(); await sessionPool.Open(); // Perform operations... await sessionPool.InsertRecordAsync("root.sg.d1", record); } catch (SessionPoolDepletedException ex) { Console.WriteLine($"Pool depleted: {ex.DepletionReason}"); Console.WriteLine($"Available clients: {ex.AvailableClients}/{ex.TotalPoolSize}"); Console.WriteLine($"Failed reconnections: {ex.FailedReconnections}"); // Implement recovery strategy (see below) }
IsOpen() is a lifecycle flag, not a health checkSessionPool.IsOpen() reports whether you have opened the pool and not yet closed it. It is not a connectivity probe:
true after a successful Open() and only returns to false when you call Close().false. Reconnection happens lazily, on the next operation.This means the following common guard never re-opens the pool, because the flag stays true forever:
// Anti-pattern: this short-circuits even while every connection is dead if (_pool != null && _pool.IsOpen()) return;
To reason about actual availability, use the health metrics below, or simply let an operation throw SessionPoolDepletedException and handle it.
Two independent timeouts govern a pool operation:
| Setting | Unit | Default | Controls |
|---|---|---|---|
SetConnectionTimeoutInMs(int) | ms | 500 | Socket-level send/receive timeout of an individual connection |
SetPoolWaitTimeoutInMs(int) | ms | 10000 | How long an operation waits for a free client before the pool gives up |
var sessionPool = new SessionPool.Builder() .SetHost("127.0.0.1") .SetPort(6667) .SetPoolSize(8) .SetConnectionTimeoutInMs(500) // socket timeout .SetPoolWaitTimeoutInMs(10_000) // give up after 10s of waiting for a free client .Build();
When the wait budget is exhausted, the operation throws SessionPoolDepletedException with the reason Connection pool is empty and wait time out(...ms). Raise SetPoolWaitTimeoutInMs if your workload legitimately queues behind long operations; lower it if you would rather fail fast and retry.
Note: before this setting existed, the wait budget was derived from the connection timeout and then misinterpreted as seconds, which turned the 500 ms default into a ~41 minute block. If you are upgrading from an older version and relied on that (unintended) long wait, set
SetPoolWaitTimeoutInMsexplicitly.
The SessionPool class exposes real-time health metrics that can be used for monitoring and alerting:
var sessionPool = new SessionPool.Builder() .SetHost("127.0.0.1") .SetPort(6667) .SetPoolSize(8) .Build(); await sessionPool.Open(); // Check pool health Console.WriteLine($"Available Clients: {sessionPool.AvailableClients}"); Console.WriteLine($"Total Pool Size: {sessionPool.TotalPoolSize}"); Console.WriteLine($"Unrealized Capacity: {sessionPool.UnrealizedCapacity}"); Console.WriteLine($"Failed Reconnections: {sessionPool.FailedReconnections}");
| Metric | Property | Description | Recommended Threshold |
|---|---|---|---|
| Available Clients | AvailableClients | Number of idle clients ready for use | Alert if < 25% of pool size |
| Total Pool Size | TotalPoolSize | Configured maximum pool size | N/A (constant) |
| Unrealized Capacity | UnrealizedCapacity | Configured capacity currently holding no connection, refilled on demand | Not an alert signal on its own - see below |
| Failed Reconnections | FailedReconnections | Cumulative count of failed reconnection attempts | Alert if > 0 and increasing |
When an operation fails and reconnection also fails, the dead connection is discarded but its capacity is retained rather than lost. UnrealizedCapacity counts the capacity left without a connection, and an acquisition that finds no idle client materializes one connection before falling back to waiting. Consequences:
Close() + Open() cycle is required.UnrealizedCapacity legitimately stays above zero long after the server has fully recovered. It measures how much of the configured pool has not been materialized, not server availability.UnrealizedCapacity alone. Use FailedReconnections to reason about server reachability: it only increases when a reconnection actually fails.Symptoms:
SessionPoolDepletedException with reason “Connection pool is empty and wait time out”AvailableClients = 0FailedReconnections = 0 or lowRoot Cause: Application workload exceeds pool capacity
Recovery Strategies:
var sessionPool = new SessionPool.Builder() .SetHost("127.0.0.1") .SetPort(6667) .SetPoolSize(16) // Increased from 8 .Build();
int maxRetries = 3; int retryDelayMs = 1000; for (int i = 0; i < maxRetries; i++) { try { await sessionPool.InsertRecordAsync(deviceId, record); break; // Success } catch (SessionPoolDepletedException ex) when (i < maxRetries - 1) { await Task.Delay(retryDelayMs * (i + 1)); // Exponential backoff } }
Symptoms:
SessionPoolDepletedException with reason “Reconnection failed”AvailableClients drops toward 0 while UnrealizedCapacity risesFailedReconnections > 0 and increasing (this, not UnrealizedCapacity, is the outage signal)Root Cause: IoTDB server unreachable or network issues
Recovery Strategies:
catch (SessionPoolDepletedException ex) { // Capacity is retained and refilled on demand - just back off and try again await Task.Delay(2000); }
catch (SessionPoolDepletedException ex) when (ex.FailedReconnections > 5) { Console.WriteLine($"Critical: {ex.FailedReconnections} failed reconnections"); // Close existing pool await sessionPool.Close(); // Wait for network recovery await Task.Delay(5000); // Create new pool sessionPool = new SessionPool.Builder() .SetHost("127.0.0.1") .SetPort(6667) .SetPoolSize(8) .Build(); await sessionPool.Open(); }
public class SessionPoolCircuitBreaker { private SessionPool _pool; private int _failureCount = 0; private const int FailureThreshold = 5; private bool _circuitOpen = false; private DateTime _lastFailureTime; public async Task<T> ExecuteAsync<T>(Func<SessionPool, Task<T>> operation) { if (_circuitOpen && DateTime.Now - _lastFailureTime < TimeSpan.FromMinutes(1)) { throw new Exception("Circuit breaker is open"); } try { var result = await operation(_pool); _failureCount = 0; // Reset on success _circuitOpen = false; return result; } catch (SessionPoolDepletedException ex) { _failureCount++; _lastFailureTime = DateTime.Now; if (_failureCount >= FailureThreshold) { _circuitOpen = true; Console.WriteLine("Circuit breaker opened - too many failures"); } throw; } } }
Symptoms:
SessionPoolDepletedExceptionRoot Cause: IoTDB server is overloaded
Recovery Strategies:
using System.Threading; private SemaphoreSlim _rateLimiter = new SemaphoreSlim(10, 10); // Max 10 concurrent operations public async Task RateLimitedInsert(string deviceId, RowRecord record) { await _rateLimiter.WaitAsync(); try { await sessionPool.InsertRecordAsync(deviceId, record); } finally { _rateLimiter.Release(); } }
var sessionPool = new SessionPool.Builder() .SetHost("127.0.0.1") .SetPort(6667) .SetConnectionTimeoutInMs(5000) // Increased socket timeout for a slow server .Build();
public class SessionPoolHealthCheck { private readonly SessionPool _pool; public SessionPoolHealthCheck(SessionPool pool) { _pool = pool; } public HealthStatus CheckHealth() { var availableRatio = (double)_pool.AvailableClients / _pool.TotalPoolSize; if (_pool.FailedReconnections > 10) { return new HealthStatus { Status = "Critical", Message = $"High reconnection failures: {_pool.FailedReconnections}", Recommendation = "Check IoTDB server availability" }; } if (availableRatio < 0.25) { return new HealthStatus { Status = "Warning", Message = $"Low available clients: {_pool.AvailableClients}/{_pool.TotalPoolSize}", Recommendation = "Consider increasing pool size" }; } return new HealthStatus { Status = "Healthy", Message = $"Pool healthy: {_pool.AvailableClients}/{_pool.TotalPoolSize} available" }; } } public class HealthStatus { public string Status { get; set; } public string Message { get; set; } public string Recommendation { get; set; } }
// Example: Export metrics to Prometheus, StatsD, or similar public class SessionPoolMetricsCollector { private readonly SessionPool _pool; public void CollectMetrics() { // Gauge: Current available clients MetricsCollector.Set("iotdb_pool_available_clients", _pool.AvailableClients); // Gauge: Total pool size MetricsCollector.Set("iotdb_pool_total_size", _pool.TotalPoolSize); // Counter: Failed reconnections MetricsCollector.Set("iotdb_pool_failed_reconnections", _pool.FailedReconnections); // Calculated: Pool utilization percentage var utilization = (1.0 - (double)_pool.AvailableClients / _pool.TotalPoolSize) * 100; MetricsCollector.Set("iotdb_pool_utilization_percent", utilization); } }
Critical Alerts:
FailedReconnections > 10: Server connectivity issuesAvailableClients == 0 for > 30 seconds: Complete pool exhaustionWarning Alerts:
AvailableClients < TotalPoolSize * 0.25: Pool under pressureFailedReconnections > 0 and increasing: Network instabilityInfo Alerts:
Pool Sizing:
Error Handling:
SessionPoolDepletedException specificallyMonitoring:
AvailableClients metricFailedReconnections as a leading indicator of problemsResource Management:
sessionPool.Close() when doneusing statements or try-finally blocks for proper cleanupusing Apache.IoTDB; using System; using System.Threading.Tasks; public class ProductionSessionPoolManager { private SessionPool _pool; private readonly object _lock = new object(); public async Task Initialize() { _pool = new SessionPool.Builder() .SetHost("127.0.0.1") .SetPort(6667) .SetPoolSize(8) .SetConnectionTimeoutInMs(5000) .Build(); await _pool.Open(); // Start health monitoring _ = Task.Run(MonitorHealth); } public async Task<T> ExecuteWithRetry<T>(Func<SessionPool, Task<T>> operation) { const int maxRetries = 3; const int baseDelayMs = 1000; for (int attempt = 0; attempt < maxRetries; attempt++) { try { return await operation(_pool); } catch (SessionPoolDepletedException ex) { Console.WriteLine($"Attempt {attempt + 1} failed: {ex.Message}"); Console.WriteLine($"Pool state - Available: {ex.AvailableClients}/{ex.TotalPoolSize}, Failed reconnections: {ex.FailedReconnections}"); if (attempt == maxRetries - 1) { // Last attempt failed if (ex.FailedReconnections > 5) { // Reinitialize pool await ReinitializePool(); } throw; } // Exponential backoff await Task.Delay(baseDelayMs * (int)Math.Pow(2, attempt)); } } throw new InvalidOperationException("Should not reach here"); } private async Task ReinitializePool() { lock (_lock) { try { _pool?.Close().Wait(); } catch { } } await Task.Delay(5000); // Wait for server recovery await Initialize(); } private async Task MonitorHealth() { while (true) { await Task.Delay(10000); // Check every 10 seconds try { var availableRatio = (double)_pool.AvailableClients / _pool.TotalPoolSize; if (_pool.FailedReconnections > 10) { Console.WriteLine($"CRITICAL: {_pool.FailedReconnections} failed reconnections"); } else if (availableRatio < 0.25) { Console.WriteLine($"WARNING: Low available clients - {_pool.AvailableClients}/{_pool.TotalPoolSize}"); } } catch (Exception ex) { Console.WriteLine($"Health check failed: {ex.Message}"); } } } public async Task Cleanup() { await _pool?.Close(); } }
The SessionPool exception handling and health monitoring features provide comprehensive tools for building robust IoTDB applications:
SessionPoolDepletedException to understand and react to pool issuesIsOpen() as a lifecycle flag, never as a connectivity checkSetPoolWaitTimeoutInMs separately from SetConnectionTimeoutInMsAvailableClients, TotalPoolSize, and FailedReconnections; read UnrealizedCapacity as capacity not yet materialized rather than as an outage signal