mirror of
https://github.com/redis/go-redis.git
synced 2025-12-02 06:22:31 +03:00
166 lines
4.9 KiB
Go
166 lines
4.9 KiB
Go
package pool
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
)
|
|
|
|
// PoolHook defines the interface for connection lifecycle hooks.
|
|
type PoolHook interface {
|
|
// OnGet is called when a connection is retrieved from the pool.
|
|
// It can modify the connection or return an error to prevent its use.
|
|
// The accept flag can be used to prevent the connection from being used.
|
|
// On Accept = false the connection is rejected and returned to the pool.
|
|
// The error can be used to prevent the connection from being used and returned to the pool.
|
|
// On Errors, the connection is removed from the pool.
|
|
// It has isNewConn flag to indicate if this is a new connection (rather than idle from the pool)
|
|
// The flag can be used for gathering metrics on pool hit/miss ratio.
|
|
OnGet(ctx context.Context, conn *Conn, isNewConn bool) (accept bool, err error)
|
|
|
|
// OnPut is called when a connection is returned to the pool.
|
|
// It returns whether the connection should be pooled and whether it should be removed.
|
|
OnPut(ctx context.Context, conn *Conn) (shouldPool bool, shouldRemove bool, err error)
|
|
|
|
// OnRemove is called when a connection is removed from the pool.
|
|
// This happens when:
|
|
// - Connection fails health check
|
|
// - Connection exceeds max lifetime
|
|
// - Pool is being closed
|
|
// - Connection encounters an error
|
|
// Implementations should clean up any per-connection state.
|
|
// The reason parameter indicates why the connection was removed.
|
|
OnRemove(ctx context.Context, conn *Conn, reason error)
|
|
}
|
|
|
|
// PoolHookManager manages multiple pool hooks.
|
|
type PoolHookManager struct {
|
|
hooks []PoolHook
|
|
hooksMu sync.RWMutex
|
|
}
|
|
|
|
// NewPoolHookManager creates a new pool hook manager.
|
|
func NewPoolHookManager() *PoolHookManager {
|
|
return &PoolHookManager{
|
|
hooks: make([]PoolHook, 0),
|
|
}
|
|
}
|
|
|
|
// AddHook adds a pool hook to the manager.
|
|
// Hooks are called in the order they were added.
|
|
func (phm *PoolHookManager) AddHook(hook PoolHook) {
|
|
phm.hooksMu.Lock()
|
|
defer phm.hooksMu.Unlock()
|
|
phm.hooks = append(phm.hooks, hook)
|
|
}
|
|
|
|
// RemoveHook removes a pool hook from the manager.
|
|
func (phm *PoolHookManager) RemoveHook(hook PoolHook) {
|
|
phm.hooksMu.Lock()
|
|
defer phm.hooksMu.Unlock()
|
|
|
|
for i, h := range phm.hooks {
|
|
if h == hook {
|
|
// Remove hook by swapping with last element and truncating
|
|
phm.hooks[i] = phm.hooks[len(phm.hooks)-1]
|
|
phm.hooks = phm.hooks[:len(phm.hooks)-1]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// ProcessOnGet calls all OnGet hooks in order.
|
|
// If any hook returns an error, processing stops and the error is returned.
|
|
func (phm *PoolHookManager) ProcessOnGet(ctx context.Context, conn *Conn, isNewConn bool) (acceptConn bool, err error) {
|
|
// Copy slice reference while holding lock (fast)
|
|
phm.hooksMu.RLock()
|
|
hooks := phm.hooks
|
|
phm.hooksMu.RUnlock()
|
|
|
|
// Call hooks without holding lock (slow operations)
|
|
for _, hook := range hooks {
|
|
acceptConn, err := hook.OnGet(ctx, conn, isNewConn)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if !acceptConn {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// ProcessOnPut calls all OnPut hooks in order.
|
|
// The first hook that returns shouldRemove=true or shouldPool=false will stop processing.
|
|
func (phm *PoolHookManager) ProcessOnPut(ctx context.Context, conn *Conn) (shouldPool bool, shouldRemove bool, err error) {
|
|
// Copy slice reference while holding lock (fast)
|
|
phm.hooksMu.RLock()
|
|
hooks := phm.hooks
|
|
phm.hooksMu.RUnlock()
|
|
|
|
shouldPool = true // Default to pooling the connection
|
|
|
|
// Call hooks without holding lock (slow operations)
|
|
for _, hook := range hooks {
|
|
hookShouldPool, hookShouldRemove, hookErr := hook.OnPut(ctx, conn)
|
|
|
|
if hookErr != nil {
|
|
return false, true, hookErr
|
|
}
|
|
|
|
// If any hook says to remove or not pool, respect that decision
|
|
if hookShouldRemove {
|
|
return false, true, nil
|
|
}
|
|
|
|
if !hookShouldPool {
|
|
shouldPool = false
|
|
}
|
|
}
|
|
|
|
return shouldPool, false, nil
|
|
}
|
|
|
|
// ProcessOnRemove calls all OnRemove hooks in order.
|
|
func (phm *PoolHookManager) ProcessOnRemove(ctx context.Context, conn *Conn, reason error) {
|
|
// Copy slice reference while holding lock (fast)
|
|
phm.hooksMu.RLock()
|
|
hooks := phm.hooks
|
|
phm.hooksMu.RUnlock()
|
|
|
|
// Call hooks without holding lock (slow operations)
|
|
for _, hook := range hooks {
|
|
hook.OnRemove(ctx, conn, reason)
|
|
}
|
|
}
|
|
|
|
// GetHookCount returns the number of registered hooks (for testing).
|
|
func (phm *PoolHookManager) GetHookCount() int {
|
|
phm.hooksMu.RLock()
|
|
defer phm.hooksMu.RUnlock()
|
|
return len(phm.hooks)
|
|
}
|
|
|
|
// GetHooks returns a copy of all registered hooks.
|
|
func (phm *PoolHookManager) GetHooks() []PoolHook {
|
|
phm.hooksMu.RLock()
|
|
defer phm.hooksMu.RUnlock()
|
|
|
|
hooks := make([]PoolHook, len(phm.hooks))
|
|
copy(hooks, phm.hooks)
|
|
return hooks
|
|
}
|
|
|
|
// Clone creates a copy of the hook manager with the same hooks.
|
|
// This is used for lock-free atomic updates of the hook manager.
|
|
func (phm *PoolHookManager) Clone() *PoolHookManager {
|
|
phm.hooksMu.RLock()
|
|
defer phm.hooksMu.RUnlock()
|
|
|
|
newManager := &PoolHookManager{
|
|
hooks: make([]PoolHook, len(phm.hooks)),
|
|
}
|
|
copy(newManager.hooks, phm.hooks)
|
|
return newManager
|
|
}
|