A high-performance, reliable WebSocket connection pool management system for Go applications.
- Features
- Installation
- Quick Start
- Usage
- Security Features
- Dynamic Adjustment
- Advanced Usage
- Performance Considerations
- Troubleshooting
- Best Practices
- License
- Lock-free design using atomic operations and
sync.Mapfor maximum performance - Thread-safe connection management with automatic lifecycle handling
- Support for both client and server connection pools
- Dynamic capacity and interval adjustment based on real-time usage patterns
- Automatic connection health monitoring with keep-alive support
- WebSocket connection pooling with efficient ID-based retrieval
- 4-byte hex connection identification for efficient tracking
- Multiple TLS security modes (self-signed, verified)
- Graceful error handling and recovery with automatic retry mechanisms
- Configurable connection creation intervals with dynamic adjustment
- Auto-reconnection on connection failures
- Built-in keep-alive management with configurable periods
- Zero lock contention for high concurrency scenarios
- Client IP whitelisting for server-side security
go get github.com/NodePassProject/npwsHere's a minimal example to get you started:
package main
import (
"log"
"time"
"github.com/NodePassProject/npws"
)
func main() {
// Create client pool
minCap := 5
maxCap := 20
minInterval := 500 * time.Millisecond
maxInterval := 5 * time.Second
keepAlive := 30 * time.Second
tlsMode := "2" // TLS mode: "1" = self-signed, "2" = verified
serverURL := "example.com:8443"
clientPool := npws.NewClientPool(
minCap, maxCap,
minInterval, maxInterval,
keepAlive,
tlsMode,
serverURL,
)
defer clientPool.Close()
// Start client manager
go clientPool.ClientManager()
// Wait for pool to be ready
time.Sleep(2 * time.Second)
// Get connection by ID (received from server)
timeout := 10 * time.Second
conn, err := clientPool.OutgoingGet("a1b2c3d4", timeout)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Use connection...
}package main
import (
"log"
"time"
"github.com/NodePassProject/npws"
)
func main() {
// Configure client pool parameters
minCap := 5 // Minimum pool capacity
maxCap := 20 // Maximum pool capacity
minInterval := 500 * time.Millisecond // Minimum creation interval
maxInterval := 5 * time.Second // Maximum creation interval
keepAlive := 30 * time.Second // Keep-alive period
tlsMode := "2" // TLS mode: "1" = self-signed, "2" = verified
serverURL := "example.com:8443" // Server address (host:port)
// Create client pool
clientPool := npws.NewClientPool(
minCap, maxCap,
minInterval, maxInterval,
keepAlive,
tlsMode,
serverURL,
)
defer clientPool.Close()
// Start pool manager in background
go clientPool.ClientManager()
// Wait for pool initialization
time.Sleep(2 * time.Second)
// Check pool status
if !clientPool.Ready() {
log.Fatal("Pool not ready")
}
log.Printf("Pool active connections: %d", clientPool.Active())
log.Printf("Pool capacity: %d", clientPool.Capacity())
log.Printf("Pool interval: %v", clientPool.Interval())
// Get connection by ID with timeout
timeout := 10 * time.Second
conn, err := clientPool.OutgoingGet("a1b2c3d4", timeout)
if err != nil {
log.Printf("Connection not found: %v", err)
return
}
defer conn.Close()
// Use connection for communication
_, err = conn.Write([]byte("Hello, Server!"))
if err != nil {
log.Printf("Write error: %v", err)
return
}
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Printf("Read error: %v", err)
return
}
log.Printf("Received: %s", string(buf[:n]))
}Note: OutgoingGet takes a connection ID and timeout duration, and returns (net.Conn, error).
The error indicates if the connection with the specified ID was not found or if the timeout was exceeded.
package main
import (
"crypto/tls"
"log"
"net"
"time"
"github.com/NodePassProject/npws"
)
func main() {
// Load TLS certificate
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Fatal(err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}
// Create TCP listener
listener, err := net.Listen("tcp", "0.0.0.0:8443")
if err != nil {
log.Fatal(err)
}
// Configure server pool
maxCap := 100 // Maximum pool capacity
clientIP := "" // Client IP whitelist ("" = allow all)
keepAlive := 30 * time.Second // Keep-alive period
// Create server pool
serverPool := npws.NewServerPool(
maxCap,
clientIP,
tlsConfig,
listener,
keepAlive,
)
if serverPool == nil {
log.Fatal("Failed to create server pool")
}
defer serverPool.Close()
// Start server manager in background
go serverPool.ServerManager()
log.Printf("Server listening on %s", listener.Addr())
// Accept connections from pool
for {
timeout := 30 * time.Second
id, conn, err := serverPool.IncomingGet(timeout)
if err != nil {
log.Printf("Failed to get connection: %v", err)
continue
}
log.Printf("New connection: %s", id)
go handleConnection(id, conn)
}
}
func handleConnection(id string, conn net.Conn) {
defer conn.Close()
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Printf("Read error: %v", err)
return
}
log.Printf("Connection %s received: %s", id, string(buf[:n]))
// Echo back
_, err = conn.Write(buf[:n])
if err != nil {
log.Printf("Write error: %v", err)
}
}Note: IncomingGet takes a timeout duration and returns (string, net.Conn, error). The return values are:
string: The connection ID generated by the server (8-character hex string)net.Conn: The connection object (wrapped WebSocket connection)error: Can indicate timeout, context cancellation, or other pool-related errors
// Get a connection from client pool by ID with timeout
timeout := 10 * time.Second
conn, err := clientPool.OutgoingGet("a1b2c3d4", timeout)
if err != nil {
// Connection with the specified ID not found or timeout exceeded
log.Printf("Connection not found: %v", err)
}
// Get a connection from server pool with timeout
timeout := 30 * time.Second
id, conn, err := serverPool.IncomingGet(timeout)
if err != nil {
// Handle various error cases:
// - Pool is full or no connections available
// - Context cancelled
// - Timeout exceeded
log.Printf("Failed to get connection: %v", err)
}
// Check if the pool is ready
if clientPool.Ready() {
// The pool is initialized and ready for use
}
// Get current active connection count
activeConns := clientPool.Active()
// Get current capacity setting
capacity := clientPool.Capacity()
// Get current connection creation interval
interval := clientPool.Interval()
// Manually flush all connections (rarely needed, closes all connections)
clientPool.Flush()
// Record an error (increases internal error counter)
clientPool.AddError()
// Get the current error count
errorCount := clientPool.ErrorCount()
// Reset the error count to zero
clientPool.ResetError()The NewServerPool function allows you to restrict incoming connections to a specific client IP address. The function signature is:
func NewServerPool(
maxCap int,
clientIP string,
tlsConfig *tls.Config,
listener net.Listener,
keepAlive time.Duration,
) *PoolmaxCap: Maximum pool capacity.clientIP: Restrict allowed client IP ("" for any).tlsConfig: TLS configuration (required for secure connections).listener: Network listener (TCP listener).keepAlive: Keep-alive period.
When the clientIP parameter is set:
- All connections from other IP addresses will be immediately rejected with HTTP 403.
- This provides an additional layer of security beyond network firewalls.
- Particularly useful for internal services or dedicated client-server applications.
To allow connections from any IP address, use an empty string:
// Create a server pool that accepts connections from any IP
serverPool := npws.NewServerPool(
100,
"",
tlsConfig,
listener,
30*time.Second,
)To restrict to a specific client IP:
// Create a server pool that only accepts connections from 192.168.1.100
serverPool := npws.NewServerPool(
100,
"192.168.1.100",
tlsConfig,
listener,
30*time.Second,
)| Mode | Description | Security Level | Use Case |
|---|---|---|---|
"1" |
Self-signed certificates (InsecureSkipVerify) | Medium | Development, testing environments |
"2" |
Verified certificates | High | Production, public networks |
Note: Both modes "1" and "2" use secure WebSocket (wss://) with TLS encryption.
// Self-signed TLS - development/testing (InsecureSkipVerify)
clientPool := npws.NewClientPool(
5, 20,
500*time.Millisecond, 5*time.Second,
30*time.Second,
"1",
"example.com:8443",
)
// Verified TLS - production
clientPool := npws.NewClientPool(
5, 20,
500*time.Millisecond, 5*time.Second,
30*time.Second,
"2",
"example.com:8443",
)Implementation Details:
-
Connection ID Generation:
- The server generates a 4-byte random ID using
crypto/rand - Encoded as 8-character hexadecimal string (e.g., "a1b2c3d4")
- IDs are unique within the pool to prevent collisions
- Sent to client immediately after WebSocket handshake
- The server generates a 4-byte random ID using
-
OutgoingGet Method:
- For client pools: Returns
(net.Conn, error)after retrieving a connection by ID. - Takes timeout parameter to wait for connection availability.
- Removes connection from pool upon successful retrieval.
- For client pools: Returns
-
IncomingGet Method:
- For server pools: Returns
(string, net.Conn, error)to get an available connection with its ID. - Blocks until a connection is available or timeout occurs.
- Returns the connection ID and connection object for further use.
- For server pools: Returns
-
Flush/Close:
Flushcloses all connections and resets the pool.Closecancels the context, shuts down HTTP server, and flushes the pool.
-
Dynamic Adjustment:
adjustIntervalandadjustCapacityare used internally for pool optimization based on usage and success rate.
-
Error Handling:
AddError,ErrorCount, andResetErrorare thread-safe using atomic operations.
-
WSConn Wrapper:
- Wraps WebSocket connection to implement
net.Conninterface. - Provides
ConnectionState()method for TLS connection state inspection. - Preserves local and remote address information.
- Wraps WebSocket connection to implement
The pool automatically adjusts parameters based on real-time metrics:
- Decreases interval (faster creation) when idle connections < 20% of capacity
- Adjustment:
interval = max(interval - 100ms, minInterval)
- Adjustment:
- Increases interval (slower creation) when idle connections > 80% of capacity
- Adjustment:
interval = min(interval + 100ms, maxInterval)
- Adjustment:
- Decreases capacity when success rate < 20%
- Adjustment:
capacity = max(capacity - 1, minCapacity)
- Adjustment:
- Increases capacity when success rate > 80%
- Adjustment:
capacity = min(capacity + 1, maxCapacity)
- Adjustment:
Monitor adjustments:
// Check current settings
currentCapacity := clientPool.Capacity() // Current target capacity
currentInterval := clientPool.Interval() // Current creation interval
activeConns := clientPool.Active() // Available connections
// Calculate utilization
utilization := float64(activeConns) / float64(currentCapacity)
log.Printf("Pool: %d/%d connections (%.1f%%), %v interval",
activeConns, currentCapacity, utilization*100, currentInterval)package main
import (
"log"
"time"
"github.com/NodePassProject/npws"
)
func main() {
clientPool := npws.NewClientPool(
5, 20,
500*time.Millisecond, 5*time.Second,
30*time.Second,
"2",
"example.com:8443",
)
defer clientPool.Close()
go clientPool.ClientManager()
// Monitor error rate
go func() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for range ticker.C {
errorCount := clientPool.ErrorCount()
if errorCount > 10 {
log.Printf("High error rate detected: %d errors", errorCount)
clientPool.ResetError()
// Take action: restart pool or alert
clientPool.Flush()
}
}
}()
// Your application logic...
time.Sleep(60 * time.Second)
}package main
import (
"context"
"log"
"time"
"github.com/NodePassProject/npws"
)
func main() {
// Create a context that can be cancelled
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
clientPool := npws.NewClientPool(
5, 20,
500*time.Millisecond, 5*time.Second,
30*time.Second,
"1",
"example.com:8443",
)
// Start manager
go clientPool.ClientManager()
// Graceful shutdown on signal
go func() {
<-ctx.Done()
log.Println("Shutting down pool...")
clientPool.Close()
}()
// Application runs until context is cancelled
<-ctx.Done()
}package main
import (
"sync/atomic"
"time"
"github.com/NodePassProject/npws"
)
func main() {
// Create pools for different servers
pools := []*npws.Pool{
npws.NewClientPool(5, 20, 500*time.Millisecond, 5*time.Second, 30*time.Second, "2", "server1.example.com:8443"),
npws.NewClientPool(5, 20, 500*time.Millisecond, 5*time.Second, 30*time.Second, "2", "server2.example.com:8443"),
npws.NewClientPool(5, 20, 500*time.Millisecond, 5*time.Second, 30*time.Second, "2", "server3.example.com:8443"),
}
// Start all managers
for _, pool := range pools {
go pool.ClientManager()
defer pool.Close()
}
// Round-robin load balancing
var counter atomic.Uint32
getNextPool := func() *npws.Pool {
idx := counter.Add(1) % uint32(len(pools))
return pools[idx]
}
// Use pools in round-robin fashion
for i := 0; i < 10; i++ {
pool := getNextPool()
conn, err := pool.OutgoingGet("some-id", 5*time.Second)
if err != nil {
pool.AddError()
continue
}
// Use connection...
conn.Close()
}
}This package uses a lock-free design for maximum concurrency:
| Component | Implementation | Benefit |
|---|---|---|
| Connection Storage | sync.Map |
Lock-free concurrent access |
| Counters | atomic.Int32 / atomic.Int64 |
Lock-free increments |
| ID Channel | Buffered chan string |
Native Go concurrency |
Performance Impact:
- Zero lock contention in high-concurrency scenarios
- No context switching overhead from mutex waits
- Scales linearly with CPU cores
- Consistent sub-microsecond operation latency
| Pool Size | Pros | Cons | Best For |
|---|---|---|---|
| Too Small (< 5) | Low resource usage | Connection contention, delays | Low-traffic applications |
| Optimal (5-50) | Balanced performance | Requires monitoring | Most applications |
| Too Large (> 100) | No contention | Resource waste, memory overhead | Very high-traffic services |
Sizing Guidelines:
- Start with
minCap = baseline_loadandmaxCap = peak_load Ă— 1.5 - Monitor connection usage with
pool.Active()andpool.Capacity() - Adjust based on observed patterns
| Aspect | WebSocket | Plain TCP |
|---|---|---|
| Handshake Time | ~50-100ms (HTTP upgrade) | ~10-20ms (direct) |
| Protocol Overhead | Minimal (frame headers) | None |
| Browser Compatibility | Excellent | Limited |
| Firewall Traversal | Easy (HTTP ports) | May be blocked |
| Throughput | ~95% of TCP | Baseline |
Connection creation in WebSocket involves:
- Cost: ~50-100ms per connection (HTTP upgrade + TLS handshake)
- Frequency: Controlled by pool intervals
- Trade-off: Fast creation vs. resource usage
For ultra-high-throughput systems, consider pre-creating connections during idle periods.
Symptoms: WebSocket connection fails to establish
Solutions:
- Check network connectivity to target host
- Verify server address and port are correct
- Ensure firewall allows WebSocket connections
- Check for proxy/NAT issues
Symptoms: Connections fail with certificate errors
Solutions:
- Verify certificate validity and expiration
- Check hostname matches certificate Common Name
- Ensure TLS 1.3 is supported
- For testing, temporarily use TLS mode
"1"(InsecureSkipVerify)
Symptoms: IncomingGet() returns timeout error
Solutions:
- Check WebSocket server status
- Increase maximum capacity
- Reduce connection hold time in application code
- Check for connection leaks (ensure connections are properly closed)
- Monitor with
pool.Active(),pool.Capacity() - Use appropriate timeout values with
IncomingGet(timeout)
Symptoms: Frequent connection creation failures
Solutions:
- Check WebSocket server stability
- Monitor network connectivity
- Verify server is accepting connections
- Track errors with
pool.AddError()andpool.ErrorCount()
Symptoms: Server manager stops with "http: Server closed"
Solutions:
- Normal behavior when
Close()is called - Check context cancellation
- Review graceful shutdown procedures
- Network connectivity: Can you reach the target host?
- Port accessibility: Is the WebSocket port open and not blocked?
- Certificate validity: For TLS, are certificates valid and not expired?
- Pool capacity: Is
maxCapsufficient for your load? - Connection leaks: Are you properly closing connections?
- Error monitoring: Are you tracking
pool.ErrorCount()? - Firewall/Proxy: Are there WebSocket-specific restrictions?
Add logging at key points for better debugging:
// Log successful connection retrieval
id, conn, err := serverPool.IncomingGet(30 * time.Second)
if err != nil {
log.Printf("Connection retrieval failed: %v", err)
serverPool.AddError()
} else {
log.Printf("Connection retrieved successfully: %s", id)
}
// Monitor pool health
ticker := time.NewTicker(10 * time.Second)
go func() {
for range ticker.C {
log.Printf("Pool status - Active: %d, Capacity: %d, Interval: %v, Errors: %d",
pool.Active(), pool.Capacity(), pool.Interval(), pool.ErrorCount())
}
}()// For most applications, start with these guidelines:
minCap := expectedConcurrentConnections
maxCap := peakConcurrentConnections * 1.5
// Example for a web service handling 50 concurrent connections
clientPool := npws.NewClientPool(
50, 75, // min/max capacity
500*time.Millisecond, // min interval
5*time.Second, // max interval
30*time.Second, // keep-alive
"2", // verified TLS
"api.example.com:8443", // server address
)
// For high-traffic API handling 200 concurrent connections
highTrafficPool := npws.NewClientPool(
150, 300, // min/max capacity
200*time.Millisecond, // min interval (faster)
3*time.Second, // max interval
30*time.Second, // keep-alive
"2", // verified TLS
"api.example.com:8443", // server address
)
log.Printf("Pool created with capacity %d-%d", minCap, maxCap)// Aggressive (high-frequency applications)
minInterval := 100 * time.Millisecond
maxInterval := 1 * time.Second
// Balanced (general purpose)
minInterval := 500 * time.Millisecond
maxInterval := 5 * time.Second
// Conservative (low-frequency, batch processing)
minInterval := 2 * time.Second
maxInterval := 10 * time.Second// The lock-free design allows safe concurrent access to pool metrics
// No need to worry about mutex contention or race conditions
func monitorPoolMetrics(pool *npws.Pool) {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
// All these operations are lock-free and thread-safe
active := pool.Active()
capacity := pool.Capacity()
interval := pool.Interval()
errors := pool.ErrorCount()
log.Printf("Pool metrics - Active: %d, Cap: %d, Interval: %v, Errors: %d",
active, capacity, interval, errors)
}
}// GOOD: Always close connections
id, conn, err := serverPool.IncomingGet(30 * time.Second)
if err != nil {
log.Printf("Failed to get connection: %v", err)
return err
}
if conn != nil {
defer conn.Close() // Close the connection when done
// Use connection...
}
// BAD: Forgetting to close connections leads to resource leaks
id, conn, _ := serverPool.IncomingGet(30 * time.Second)
// Missing Close() - causes resource leak!// Use reasonable timeouts for IncomingGet
timeout := 10 * time.Second
id, conn, err := serverPool.IncomingGet(timeout)
if err != nil {
log.Printf("Connection timeout: %v", err)
serverPool.AddError()
return err
}
if conn == nil {
log.Printf("Unexpected nil connection")
return errors.New("unexpected nil connection")
}
defer conn.Close()type PoolManager struct {
pool *npws.Pool
maxErrors int
logger *log.Logger
}
func (pm *PoolManager) getConnectionWithRetry(id string, maxRetries int) (net.Conn, error) {
for i := 0; i < maxRetries; i++ {
conn, err := pm.pool.OutgoingGet(id, 5*time.Second)
if err == nil {
return conn, nil
}
pm.pool.AddError()
pm.logger.Printf("Retry %d/%d: %v", i+1, maxRetries, err)
time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
}
return nil, errors.New("max retries exceeded")
}
// Monitor pool health periodically
func (pm *PoolManager) healthCheck() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
errorCount := pm.pool.ErrorCount()
active := pm.pool.Active()
capacity := pm.pool.Capacity()
if errorCount > pm.maxErrors {
pm.logger.Printf("High error rate: %d errors", errorCount)
pm.pool.Flush()
pm.pool.ResetError()
}
utilization := float64(active) / float64(capacity) * 100
pm.logger.Printf("Pool health - Utilization: %.1f%%, Errors: %d",
utilization, errorCount)
}
}// Production server setup with proper TLS
func createProductionServer() (*npws.Pool, error) {
// Load production certificates
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
},
}
listener, err := net.Listen("tcp", "0.0.0.0:8443")
if err != nil {
return nil, err
}
// Restrict to specific client IP for security
return npws.NewServerPool(
100,
"192.168.1.100", // Only allow this client
tlsConfig,
listener,
30*time.Second,
), nil
}
// Client with verified certificates
func createProductionClient() *npws.Pool {
return npws.NewClientPool(
20, 50,
500*time.Millisecond,
5*time.Second,
30*time.Second,
"2", // Verified TLS
"api.example.com:8443",
)
}func (app *Application) Shutdown(ctx context.Context) error {
// Stop accepting new requests first
app.isShuttingDown.Store(true)
// Give existing connections time to complete
shutdownTimeout := 10 * time.Second
shutdownCtx, cancel := context.WithTimeout(ctx, shutdownTimeout)
defer cancel()
// Close all pools
for _, pool := range app.pools {
pool.Close()
}
<-shutdownCtx.Done()
return nil
}// ANTI-PATTERN: Creating pools repeatedly
func badHandler(w http.ResponseWriter, r *http.Request) {
// DON'T: Create a new pool for each request
pool := npws.NewClientPool(5, 20, 500*time.Millisecond, 5*time.Second, 30*time.Second, "2", "server:8443")
defer pool.Close()
}
// GOOD PATTERN: Reuse pools
type Server struct {
wsPool *npws.Pool // Shared pool instance
}
func (s *Server) goodHandler(w http.ResponseWriter, r *http.Request) {
// DO: Reuse existing pool
conn, err := s.wsPool.OutgoingGet("conn-id", 5*time.Second)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer conn.Close()
// Handle request...
}// High-throughput, low-latency services
highThroughputPool := npws.NewClientPool(
100, 200, // Large capacity
100*time.Millisecond, // Fast creation
1*time.Second, // Quick adjustment
15*time.Second, // Short keep-alive
"2",
"api.example.com:8443",
)
// Long-running, stable connections
stablePool := npws.NewClientPool(
10, 20, // Smaller capacity
1*time.Second, // Slower creation
10*time.Second, // Longer adjustment
60*time.Second, // Long keep-alive
"2",
"backend.example.com:8443",
)
// Batch processing with bursts
batchPool := npws.NewClientPool(
5, 50, // Wide capacity range
2*time.Second, // Slow baseline
10*time.Second, // Wide adjustment range
30*time.Second, // Standard keep-alive
"2",
"batch.example.com:8443",
)// Development setup with relaxed security
func createDevPool() *npws.Pool {
return npws.NewClientPool(
2, 5, // Small capacity for testing
1*time.Second, // Slow intervals for observation
5*time.Second,
10*time.Second, // Short keep-alive
"1", // Self-signed certs (InsecureSkipVerify)
"localhost:8443",
)
}func TestPoolIntegration(t *testing.T) {
// Setup server
cert, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key")
if err != nil {
t.Fatal(err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
serverPool := npws.NewServerPool(10, "", tlsConfig, listener, 10*time.Second)
go serverPool.ServerManager()
defer serverPool.Close()
// Setup client
clientPool := npws.NewClientPool(
2, 5,
500*time.Millisecond, 2*time.Second,
10*time.Second,
"1", // Self-signed certs for testing
listener.Addr().String(),
)
go clientPool.ClientManager()
defer clientPool.Close()
// Wait for pool initialization
time.Sleep(2 * time.Second)
// Test connection flow
id, conn, err := serverPool.IncomingGet(5 * time.Second)
if err != nil {
t.Fatalf("Failed to get connection: %v", err)
}
defer conn.Close()
t.Logf("Connection established with ID: %s", id)
}This project is licensed under the BSD 3-Clause License.
See the LICENSE file for details.