kevo/pkg/grpc/transport/pool.go

210 lines
4.8 KiB
Go

package transport
import (
"context"
"errors"
"sync"
"time"
)
var (
ErrPoolClosed = errors.New("connection pool is closed")
ErrPoolFull = errors.New("connection pool is full")
ErrPoolEmptyNoWait = errors.New("connection pool is empty")
)
// ConnectionPool manages a pool of gRPC connections
type ConnectionPool struct {
manager *GRPCTransportManager
address string
maxIdle int
maxActive int
idlePool chan *GRPCConnection
activePool chan struct{}
mu sync.Mutex
closed bool
idleTime time.Duration
}
// NewConnectionPool creates a new connection pool
func NewConnectionPool(manager *GRPCTransportManager, address string, maxIdle, maxActive int, idleTime time.Duration) *ConnectionPool {
if maxIdle <= 0 {
maxIdle = 2
}
if maxActive <= 0 {
maxActive = 10
}
if idleTime <= 0 {
idleTime = 5 * time.Minute
}
pool := &ConnectionPool{
manager: manager,
address: address,
maxIdle: maxIdle,
maxActive: maxActive,
idlePool: make(chan *GRPCConnection, maxIdle),
activePool: make(chan struct{}, maxActive),
idleTime: idleTime,
}
return pool
}
// Get retrieves a connection from the pool or creates a new one
func (p *ConnectionPool) Get(ctx context.Context, wait bool) (*GRPCConnection, error) {
p.mu.Lock()
if p.closed {
p.mu.Unlock()
return nil, ErrPoolClosed
}
p.mu.Unlock()
// Try to get an idle connection
select {
case conn := <-p.idlePool:
return conn, nil
default:
// No idle connections available
}
// Check if we can create a new connection
select {
case p.activePool <- struct{}{}:
// We acquired a slot to create a new connection
conn, err := p.createConnection(ctx)
if err != nil {
// If connection creation fails, release the active slot
<-p.activePool
return nil, err
}
return conn, nil
default:
// Pool is full, check if we should wait
if !wait {
return nil, ErrPoolEmptyNoWait
}
}
// Wait for a connection to become available or context to expire
select {
case conn := <-p.idlePool:
// Got an idle connection
return conn, nil
case p.activePool <- struct{}{}:
// Got permission to create a new connection
conn, err := p.createConnection(ctx)
if err != nil {
<-p.activePool
return nil, err
}
return conn, nil
case <-ctx.Done():
// Context deadline exceeded or canceled
return nil, ctx.Err()
}
}
// createConnection creates a new connection to the server
func (p *ConnectionPool) createConnection(ctx context.Context) (*GRPCConnection, error) {
conn, err := p.manager.Connect(ctx, p.address)
if err != nil {
return nil, err
}
// Convert to our internal type
grpcConn, ok := conn.(*GRPCConnection)
if !ok {
conn.Close()
return nil, errors.New("invalid connection type")
}
return grpcConn, nil
}
// Put returns a connection to the pool
func (p *ConnectionPool) Put(conn *GRPCConnection) error {
p.mu.Lock()
if p.closed {
p.mu.Unlock()
conn.Close() // Close the connection since the pool is closed
<-p.activePool
return nil
}
p.mu.Unlock()
// Try to add to the idle pool
select {
case p.idlePool <- conn:
// Successfully returned to idle pool
return nil
default:
// Idle pool full, close the connection
conn.Close()
<-p.activePool
return nil
}
}
// Close closes the connection pool and all idle connections
func (p *ConnectionPool) Close() error {
p.mu.Lock()
defer p.mu.Unlock()
if p.closed {
return ErrPoolClosed
}
p.closed = true
// Close all idle connections
close(p.idlePool)
for conn := range p.idlePool {
conn.Close()
<-p.activePool
}
return nil
}
// ConnectionPoolManager manages multiple connection pools
type ConnectionPoolManager struct {
manager *GRPCTransportManager
pools sync.Map // map[string]*ConnectionPool
defaultMaxIdle int
defaultMaxActive int
defaultIdleTime time.Duration
}
// NewConnectionPoolManager creates a new connection pool manager
func NewConnectionPoolManager(manager *GRPCTransportManager, maxIdle, maxActive int, idleTime time.Duration) *ConnectionPoolManager {
return &ConnectionPoolManager{
manager: manager,
defaultMaxIdle: maxIdle,
defaultMaxActive: maxActive,
defaultIdleTime: idleTime,
}
}
// GetPool gets or creates a connection pool for the given address
func (m *ConnectionPoolManager) GetPool(address string) *ConnectionPool {
// Check if pool exists
if pool, ok := m.pools.Load(address); ok {
return pool.(*ConnectionPool)
}
// Create new pool
pool := NewConnectionPool(m.manager, address, m.defaultMaxIdle, m.defaultMaxActive, m.defaultIdleTime)
m.pools.Store(address, pool)
return pool
}
// CloseAll closes all connection pools
func (m *ConnectionPoolManager) CloseAll() {
m.pools.Range(func(key, value interface{}) bool {
pool := value.(*ConnectionPool)
pool.Close()
m.pools.Delete(key)
return true
})
}