mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
fix: 移除隧道连接时检测是否正在使用
This commit is contained in:
@@ -8,35 +8,27 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var CachePoolDefaultConfig = PoolConfig{
|
||||
MaxConns: 1,
|
||||
IdleTimeout: 60 * time.Minute,
|
||||
WaitTimeout: 10 * time.Second,
|
||||
HealthCheckInterval: 10 * time.Minute,
|
||||
}
|
||||
|
||||
type cacheEntry[T Conn] struct {
|
||||
conn T
|
||||
lastActive time.Time
|
||||
}
|
||||
|
||||
func (e *cacheEntry[T]) Close() {
|
||||
if err := e.conn.Close(); err != nil {
|
||||
logx.Errorf("cache pool - closing connection error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type CachePool[T Conn] struct {
|
||||
factory func() (T, error)
|
||||
mu sync.RWMutex
|
||||
cache map[string]*cacheEntry[T] // 使用字符串键的缓存
|
||||
config PoolConfig
|
||||
config PoolConfig[T]
|
||||
closeCh chan struct{}
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewCachePool[T Conn](factory func() (T, error), opts ...Option) *CachePool[T] {
|
||||
config := CachePoolDefaultConfig
|
||||
func NewCachePool[T Conn](factory func() (T, error), opts ...Option[T]) *CachePool[T] {
|
||||
config := PoolConfig[T]{
|
||||
MaxConns: 1,
|
||||
IdleTimeout: 60 * time.Minute,
|
||||
WaitTimeout: 10 * time.Second,
|
||||
HealthCheckInterval: 10 * time.Minute,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&config)
|
||||
}
|
||||
@@ -81,8 +73,9 @@ func (p *CachePool[T]) Get(ctx context.Context) (T, error) {
|
||||
return entry.conn, nil
|
||||
}
|
||||
// 清理超时连接
|
||||
entry.Close()
|
||||
delete(p.cache, key)
|
||||
if !p.closeConn(key, entry, false) {
|
||||
return entry.conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新连接
|
||||
@@ -151,8 +144,9 @@ func (p *CachePool[T]) Close() {
|
||||
p.closed = true
|
||||
close(p.closeCh)
|
||||
|
||||
for _, entry := range p.cache {
|
||||
entry.Close()
|
||||
for key, entry := range p.cache {
|
||||
// 强制关闭连接
|
||||
p.closeConn(key, entry, true)
|
||||
}
|
||||
|
||||
// 触发关闭回调
|
||||
@@ -212,13 +206,33 @@ func (p *CachePool[T]) cleanupIdle() {
|
||||
|
||||
cutoff := time.Now().Add(-p.config.IdleTimeout)
|
||||
for key, entry := range p.cache {
|
||||
if entry.lastActive.Before(cutoff) {
|
||||
entry.Close()
|
||||
delete(p.cache, key)
|
||||
if entry.lastActive.Before(cutoff) || entry.conn.Ping() != nil {
|
||||
logx.Infof("cache pool - cleaning up idle connection, key: %s", key)
|
||||
// 如果连接超时或不可用,则关闭连接
|
||||
p.closeConn(key, entry, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *CachePool[T]) closeConn(key string, entry *cacheEntry[T], force bool) bool {
|
||||
if !force {
|
||||
// 如果不是强制关闭且有连接关闭回调,则调用回调
|
||||
// 如果回调返回错误,则不关闭连接
|
||||
if onConnClose := p.config.OnConnClose; onConnClose != nil {
|
||||
if err := onConnClose(entry.conn); err != nil {
|
||||
logx.Infof("cache pool - connection close callback returned error, skip closing connection:: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := entry.conn.Close(); err != nil {
|
||||
logx.Errorf("cache pool - closing connection error: %v", err)
|
||||
}
|
||||
delete(p.cache, key)
|
||||
return true
|
||||
}
|
||||
|
||||
// 生成缓存键
|
||||
func generateCacheKey() string {
|
||||
return stringx.RandUUID()
|
||||
|
||||
@@ -10,13 +10,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var ChanPoolDefaultConfig = PoolConfig{
|
||||
MaxConns: 5,
|
||||
IdleTimeout: 60 * time.Minute,
|
||||
WaitTimeout: 10 * time.Second,
|
||||
HealthCheckInterval: 10 * time.Minute,
|
||||
}
|
||||
|
||||
// chanConn 封装连接及其元数据
|
||||
type chanConn[T Conn] struct {
|
||||
conn T
|
||||
@@ -41,7 +34,7 @@ type ChanPool[T Conn] struct {
|
||||
mu sync.RWMutex
|
||||
factory func() (T, error)
|
||||
idleConns chan *chanConn[T]
|
||||
config PoolConfig
|
||||
config PoolConfig[T]
|
||||
currentConns int32
|
||||
stats PoolStats
|
||||
closeChan chan struct{} // 用于关闭健康检查 goroutine
|
||||
@@ -56,9 +49,14 @@ type PoolStats struct {
|
||||
WaitCount int64 // 等待连接次数
|
||||
}
|
||||
|
||||
func NewChannelPool[T Conn](factory func() (T, error), opts ...Option) *ChanPool[T] {
|
||||
func NewChannelPool[T Conn](factory func() (T, error), opts ...Option[T]) *ChanPool[T] {
|
||||
// 1. 初始化配置(使用默认值 + Option 覆盖)
|
||||
config := ChanPoolDefaultConfig
|
||||
config := PoolConfig[T]{
|
||||
MaxConns: 5,
|
||||
IdleTimeout: 60 * time.Minute,
|
||||
WaitTimeout: 10 * time.Second,
|
||||
HealthCheckInterval: 10 * time.Minute,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&config)
|
||||
}
|
||||
|
||||
@@ -8,48 +8,57 @@ import (
|
||||
var ErrPoolClosed = errors.New("pool is closed")
|
||||
|
||||
// PoolConfig 连接池配置
|
||||
type PoolConfig struct {
|
||||
type PoolConfig[T Conn] struct {
|
||||
MaxConns int // 最大连接数
|
||||
IdleTimeout time.Duration // 空闲连接超时时间
|
||||
WaitTimeout time.Duration // 获取连接超时时间
|
||||
HealthCheckInterval time.Duration // 健康检查间隔
|
||||
OnPoolClose func() error // 连接池关闭时的回调
|
||||
|
||||
OnConnClose func(conn T) error // 连接关闭时的回调,若err != nil则不关闭连接
|
||||
}
|
||||
|
||||
// Option 函数类型,用于配置 Pool
|
||||
type Option func(*PoolConfig)
|
||||
type Option[T Conn] func(*PoolConfig[T])
|
||||
|
||||
// WithMaxConns 设置最大连接数
|
||||
func WithMaxConns(maxConns int) Option {
|
||||
return func(c *PoolConfig) {
|
||||
func WithMaxConns[T Conn](maxConns int) Option[T] {
|
||||
return func(c *PoolConfig[T]) {
|
||||
c.MaxConns = maxConns
|
||||
}
|
||||
}
|
||||
|
||||
// WithIdleTimeout 设置空闲超时
|
||||
func WithIdleTimeout(timeout time.Duration) Option {
|
||||
return func(c *PoolConfig) {
|
||||
func WithIdleTimeout[T Conn](timeout time.Duration) Option[T] {
|
||||
return func(c *PoolConfig[T]) {
|
||||
c.IdleTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithWaitTimeout 设置等待超时
|
||||
func WithWaitTimeout(timeout time.Duration) Option {
|
||||
return func(c *PoolConfig) {
|
||||
func WithWaitTimeout[T Conn](timeout time.Duration) Option[T] {
|
||||
return func(c *PoolConfig[T]) {
|
||||
c.WaitTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithHealthCheckInterval 设置健康检查间隔
|
||||
func WithHealthCheckInterval(interval time.Duration) Option {
|
||||
return func(c *PoolConfig) {
|
||||
func WithHealthCheckInterval[T Conn](interval time.Duration) Option[T] {
|
||||
return func(c *PoolConfig[T]) {
|
||||
c.HealthCheckInterval = interval
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnPoolClose 设置连接池关闭回调
|
||||
func WithOnPoolClose(fn func() error) Option {
|
||||
return func(c *PoolConfig) {
|
||||
func WithOnPoolClose[T Conn](fn func() error) Option[T] {
|
||||
return func(c *PoolConfig[T]) {
|
||||
c.OnPoolClose = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnConnClose 设置连接关闭回调, 若返回的错误不为nil,则不关闭连接
|
||||
func WithOnConnClose[T Conn](fn func(conn T) error) Option[T] {
|
||||
return func(c *PoolConfig[T]) {
|
||||
c.OnConnClose = fn
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func NewPoolGroup[T Conn]() *PoolGroup[T] {
|
||||
func (pg *PoolGroup[T]) GetOrCreate(
|
||||
key string,
|
||||
poolFactory func() Pool[T],
|
||||
opts ...Option,
|
||||
opts ...Option[T],
|
||||
) (Pool[T], error) {
|
||||
// 先尝试读锁获取
|
||||
pg.mu.RLock()
|
||||
@@ -63,19 +63,29 @@ func (pg *PoolGroup[T]) GetOrCreate(
|
||||
}
|
||||
|
||||
// GetChanPool 获取或创建 ChannelPool 类型连接池
|
||||
func (pg *PoolGroup[T]) GetChanPool(key string, factory func() (T, error), opts ...Option) (Pool[T], error) {
|
||||
func (pg *PoolGroup[T]) GetChanPool(key string, factory func() (T, error), opts ...Option[T]) (Pool[T], error) {
|
||||
return pg.GetOrCreate(key, func() Pool[T] {
|
||||
return NewChannelPool(factory, opts...)
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// GetCachePool 获取或创建 CachePool 类型连接池
|
||||
func (pg *PoolGroup[T]) GetCachePool(key string, factory func() (T, error), opts ...Option) (Pool[T], error) {
|
||||
func (pg *PoolGroup[T]) GetCachePool(key string, factory func() (T, error), opts ...Option[T]) (Pool[T], error) {
|
||||
return pg.GetOrCreate(key, func() Pool[T] {
|
||||
return NewCachePool(factory, opts...)
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// Get 获取指定 key 的连接池
|
||||
func (pg *PoolGroup[T]) Get(key string) (Pool[T], bool) {
|
||||
pg.mu.RLock()
|
||||
defer pg.mu.RUnlock()
|
||||
if p, ok := pg.poolGroup[key]; ok {
|
||||
return p, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (pg *PoolGroup[T]) Close(key string) error {
|
||||
pg.mu.Lock()
|
||||
defer pg.mu.Unlock()
|
||||
|
||||
@@ -88,10 +88,10 @@ func newMockConn(id int) *mockConn {
|
||||
|
||||
func TestChanPool_Basic(t *testing.T) {
|
||||
var idGen int
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
idGen++
|
||||
return newMockConn(idGen), nil
|
||||
}, WithMaxConns(2), WithIdleTimeout(time.Second))
|
||||
}, WithMaxConns[*mockConn](2), WithIdleTimeout[*mockConn](time.Second))
|
||||
|
||||
ctx := context.Background()
|
||||
conn1, _ := pool.Get(ctx)
|
||||
@@ -112,9 +112,9 @@ func TestChanPool_Basic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChanPool_WaitTimeout(t *testing.T) {
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(1), WithWaitTimeout(100*time.Millisecond))
|
||||
}, WithMaxConns[*mockConn](1), WithWaitTimeout[*mockConn](100*time.Millisecond))
|
||||
|
||||
ctx := context.Background()
|
||||
conn1, _ := pool.Get(ctx)
|
||||
@@ -132,9 +132,9 @@ func TestChanPool_WaitTimeout(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChanPool_ContextCancel(t *testing.T) {
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(1))
|
||||
}, WithMaxConns[*mockConn](1))
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
conn, _ := pool.Get(ctx)
|
||||
@@ -145,9 +145,9 @@ func TestChanPool_ContextCancel(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChanPool_Resize(t *testing.T) {
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(2))
|
||||
}, WithMaxConns[*mockConn](2))
|
||||
ctx := context.Background()
|
||||
conn1, _ := pool.Get(ctx)
|
||||
conn2, _ := pool.Get(ctx)
|
||||
@@ -158,9 +158,9 @@ func TestChanPool_Resize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChanPool_HealthCheck(t *testing.T) {
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(1), WithIdleTimeout(10*time.Millisecond), WithHealthCheckInterval(10*time.Millisecond))
|
||||
}, WithMaxConns[*mockConn](1), WithIdleTimeout[*mockConn](10*time.Millisecond), WithHealthCheckInterval[*mockConn](10*time.Millisecond))
|
||||
ctx := context.Background()
|
||||
conn, _ := pool.Get(ctx)
|
||||
_ = pool.Put(conn)
|
||||
@@ -176,10 +176,10 @@ func TestChanPool_HealthCheck(t *testing.T) {
|
||||
|
||||
func TestCachePool_Basic(t *testing.T) {
|
||||
var idGen int
|
||||
pool := NewCachePool(func() (Conn, error) {
|
||||
pool := NewCachePool(func() (*mockConn, error) {
|
||||
idGen++
|
||||
return newMockConn(idGen), nil
|
||||
}, WithMaxConns(2), WithIdleTimeout(time.Second))
|
||||
}, WithMaxConns[*mockConn](2), WithIdleTimeout[*mockConn](time.Second))
|
||||
|
||||
ctx := context.Background()
|
||||
conn1, _ := pool.Get(ctx)
|
||||
@@ -193,9 +193,9 @@ func TestCachePool_Basic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCachePool_TimeoutCleanup(t *testing.T) {
|
||||
pool := NewCachePool(func() (Conn, error) {
|
||||
pool := NewCachePool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(1), WithIdleTimeout(10*time.Millisecond), WithHealthCheckInterval(10*time.Millisecond))
|
||||
}, WithMaxConns[*mockConn](1), WithIdleTimeout[*mockConn](10*time.Millisecond), WithHealthCheckInterval[*mockConn](10*time.Millisecond))
|
||||
ctx := context.Background()
|
||||
conn, _ := pool.Get(ctx)
|
||||
_ = pool.Put(conn)
|
||||
@@ -209,10 +209,10 @@ func TestCachePool_TimeoutCleanup(t *testing.T) {
|
||||
|
||||
func TestCachePool_OverMaxConns(t *testing.T) {
|
||||
var idGen int
|
||||
pool := NewCachePool(func() (Conn, error) {
|
||||
pool := NewCachePool(func() (*mockConn, error) {
|
||||
idGen++
|
||||
return newMockConn(idGen), nil
|
||||
}, WithMaxConns(1))
|
||||
}, WithMaxConns[*mockConn](1))
|
||||
ctx := context.Background()
|
||||
conn1, _ := pool.Get(ctx)
|
||||
_ = pool.Put(conn1)
|
||||
@@ -231,9 +231,9 @@ func TestCachePool_OverMaxConns(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCachePool_Resize(t *testing.T) {
|
||||
pool := NewCachePool(func() (Conn, error) {
|
||||
pool := NewCachePool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(2))
|
||||
}, WithMaxConns[*mockConn](2))
|
||||
ctx := context.Background()
|
||||
conn1, _ := pool.Get(ctx)
|
||||
_ = pool.Put(conn1)
|
||||
@@ -288,9 +288,9 @@ func TestPoolGroup_Concurrent(t *testing.T) {
|
||||
// ========== 压力测试 ==========
|
||||
|
||||
func BenchmarkChanPool_Concurrent(b *testing.B) {
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(100))
|
||||
}, WithMaxConns[*mockConn](100))
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
@@ -307,9 +307,9 @@ func BenchmarkChanPool_Concurrent(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkCachePool_Concurrent(b *testing.B) {
|
||||
pool := NewCachePool(func() (Conn, error) {
|
||||
pool := NewCachePool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(100))
|
||||
}, WithMaxConns[*mockConn](100))
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
@@ -332,9 +332,9 @@ func TestChanPool_Stress(t *testing.T) {
|
||||
iterations = 1000
|
||||
)
|
||||
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(20), WithWaitTimeout(time.Second))
|
||||
}, WithMaxConns[*mockConn](20), WithWaitTimeout[*mockConn](time.Second))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errCount int32
|
||||
@@ -389,9 +389,9 @@ func TestCachePool_Stress(t *testing.T) {
|
||||
iterations = 1000
|
||||
)
|
||||
|
||||
pool := NewCachePool(func() (Conn, error) {
|
||||
pool := NewCachePool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(20), WithIdleTimeout(time.Minute))
|
||||
}, WithMaxConns[*mockConn](20), WithIdleTimeout[*mockConn](time.Minute))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errCount int32
|
||||
@@ -430,11 +430,11 @@ func TestCachePool_Stress(t *testing.T) {
|
||||
|
||||
// 测试连接池在连接失效时的行为
|
||||
func TestChanPool_InvalidConn(t *testing.T) {
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
conn := newMockConn(1)
|
||||
conn.pingErr = errors.New("connection invalid")
|
||||
return conn, nil
|
||||
}, WithMaxConns(1), WithHealthCheckInterval(10*time.Millisecond))
|
||||
}, WithMaxConns[*mockConn](1), WithHealthCheckInterval[*mockConn](10*time.Millisecond))
|
||||
|
||||
ctx := context.Background()
|
||||
conn, _ := pool.Get(ctx)
|
||||
@@ -458,9 +458,9 @@ func TestChanPool_InvalidConn(t *testing.T) {
|
||||
|
||||
// 测试连接池在并发关闭时的行为
|
||||
func TestChanPool_ConcurrentClose(t *testing.T) {
|
||||
pool := NewChannelPool(func() (Conn, error) {
|
||||
pool := NewChannelPool(func() (*mockConn, error) {
|
||||
return newMockConn(1), nil
|
||||
}, WithMaxConns(10))
|
||||
}, WithMaxConns[*mockConn](10))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const goroutines = 10
|
||||
|
||||
Reference in New Issue
Block a user