e071084ebe
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
189 lines
3.9 KiB
Go
189 lines
3.9 KiB
Go
package nats
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
natsp "github.com/nats-io/nats.go"
|
|
)
|
|
|
|
var (
|
|
// ErrPoolExhausted is returned when no connections are available in the pool
|
|
ErrPoolExhausted = errors.New("connection pool exhausted")
|
|
// ErrPoolClosed is returned when trying to use a closed pool
|
|
ErrPoolClosed = errors.New("connection pool is closed")
|
|
)
|
|
|
|
// connectionPool manages a pool of NATS connections
|
|
type connectionPool struct {
|
|
mu sync.RWMutex
|
|
connections chan *pooledConnection
|
|
factory func() (*natsp.Conn, error)
|
|
size int
|
|
idleTimeout time.Duration
|
|
closed bool
|
|
}
|
|
|
|
// pooledConnection wraps a NATS connection with metadata
|
|
type pooledConnection struct {
|
|
conn *natsp.Conn
|
|
createdAt time.Time
|
|
lastUsed time.Time
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// newConnectionPool creates a new connection pool
|
|
func newConnectionPool(size int, factory func() (*natsp.Conn, error)) (*connectionPool, error) {
|
|
if size <= 0 {
|
|
size = 1
|
|
}
|
|
|
|
pool := &connectionPool{
|
|
connections: make(chan *pooledConnection, size),
|
|
factory: factory,
|
|
size: size,
|
|
idleTimeout: 5 * time.Minute,
|
|
closed: false,
|
|
}
|
|
|
|
return pool, nil
|
|
}
|
|
|
|
// Get retrieves a connection from the pool or creates a new one
|
|
func (p *connectionPool) Get() (*pooledConnection, error) {
|
|
p.mu.RLock()
|
|
if p.closed {
|
|
p.mu.RUnlock()
|
|
return nil, ErrPoolClosed
|
|
}
|
|
p.mu.RUnlock()
|
|
|
|
// Try to get an existing connection from the pool
|
|
select {
|
|
case conn := <-p.connections:
|
|
// Check if connection is still valid and not idle for too long
|
|
if conn.isValid() && !conn.isExpired(p.idleTimeout) {
|
|
conn.updateLastUsed()
|
|
return conn, nil
|
|
}
|
|
// Connection is invalid or expired, close it and create a new one
|
|
conn.close()
|
|
return p.createConnection()
|
|
default:
|
|
// No connection available, create a new one
|
|
return p.createConnection()
|
|
}
|
|
}
|
|
|
|
// Put returns a connection to the pool
|
|
func (p *connectionPool) Put(conn *pooledConnection) error {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
if p.closed {
|
|
return conn.close()
|
|
}
|
|
|
|
// Check if connection is still valid
|
|
if !conn.isValid() {
|
|
return conn.close()
|
|
}
|
|
|
|
conn.updateLastUsed()
|
|
|
|
// Try to return connection to pool
|
|
select {
|
|
case p.connections <- conn:
|
|
return nil
|
|
default:
|
|
// Pool is full, close the connection
|
|
return conn.close()
|
|
}
|
|
}
|
|
|
|
// Close closes all connections in the pool
|
|
func (p *connectionPool) Close() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.closed {
|
|
return nil
|
|
}
|
|
|
|
p.closed = true
|
|
close(p.connections)
|
|
|
|
// Close all connections in the pool
|
|
for conn := range p.connections {
|
|
conn.close()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createConnection creates a new pooled connection
|
|
func (p *connectionPool) createConnection() (*pooledConnection, error) {
|
|
conn, err := p.factory()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &pooledConnection{
|
|
conn: conn,
|
|
createdAt: time.Now(),
|
|
lastUsed: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// isValid checks if the underlying NATS connection is valid
|
|
func (pc *pooledConnection) isValid() bool {
|
|
pc.mu.Lock()
|
|
defer pc.mu.Unlock()
|
|
|
|
if pc.conn == nil {
|
|
return false
|
|
}
|
|
|
|
status := pc.conn.Status()
|
|
return status == natsp.CONNECTED || status == natsp.RECONNECTING
|
|
}
|
|
|
|
// isExpired checks if the connection has been idle for too long
|
|
func (pc *pooledConnection) isExpired(timeout time.Duration) bool {
|
|
pc.mu.Lock()
|
|
defer pc.mu.Unlock()
|
|
|
|
if timeout <= 0 {
|
|
return false
|
|
}
|
|
|
|
return time.Since(pc.lastUsed) > timeout
|
|
}
|
|
|
|
// close closes the underlying NATS connection
|
|
func (pc *pooledConnection) close() error {
|
|
pc.mu.Lock()
|
|
defer pc.mu.Unlock()
|
|
|
|
if pc.conn != nil {
|
|
pc.conn.Close()
|
|
pc.conn = nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Conn returns the underlying NATS connection
|
|
func (pc *pooledConnection) Conn() *natsp.Conn {
|
|
pc.mu.Lock()
|
|
defer pc.mu.Unlock()
|
|
return pc.conn
|
|
}
|
|
|
|
// updateLastUsed updates the last used timestamp in a thread-safe manner
|
|
func (pc *pooledConnection) updateLastUsed() {
|
|
pc.mu.Lock()
|
|
defer pc.mu.Unlock()
|
|
pc.lastUsed = time.Now()
|
|
}
|