mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-25 02:20:30 +08:00
fix: ssh tunnel检测导致死锁问题调整
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-dialog :title="title" v-model="dialogVisible" :show-close="true" width="1000px" @close="close()">
|
<el-dialog
|
||||||
|
:title="title"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:show-close="true"
|
||||||
|
width="1000px"
|
||||||
|
@close="close()"
|
||||||
|
body-class="h-[65vh] overflow-y-auto overflow-x-hidden"
|
||||||
|
>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :lg="16" :md="16">
|
<el-col :lg="16" :md="16">
|
||||||
<el-descriptions class="redis-info info-server" :title="$t('redis.redisInfoTitle')" :column="3" size="small" border>
|
<el-descriptions class="redis-info info-server" :title="$t('redis.redisInfoTitle')" :column="3" size="small" border>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func init() {
|
|||||||
if v.Stats().TotalConns == 0 {
|
if v.Stats().TotalConns == 0 {
|
||||||
continue // 连接池中没有连接,跳过
|
continue // 连接池中没有连接,跳过
|
||||||
}
|
}
|
||||||
conn, err := v.Get(context.Background())
|
conn, err := v.Get(context.Background(), pool.WithNoUpdateLastActive())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // 获取连接失败,跳过
|
continue // 获取连接失败,跳过
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func init() {
|
|||||||
if v.Stats().TotalConns == 0 {
|
if v.Stats().TotalConns == 0 {
|
||||||
continue // 连接池中没有连接,跳过
|
continue // 连接池中没有连接,跳过
|
||||||
}
|
}
|
||||||
conn, err := v.Get(context.Background())
|
conn, err := v.Get(context.Background(), pool.WithNoUpdateLastActive())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // 获取连接失败,跳过
|
continue // 获取连接失败,跳过
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package mcm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"mayfly-go/pkg/pool"
|
"mayfly-go/pkg/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,11 +52,11 @@ func GetMachineCli(ctx context.Context, authCertName string, getMachine func(str
|
|||||||
|
|
||||||
// 删除指定机器缓存客户端,并关闭客户端连接
|
// 删除指定机器缓存客户端,并关闭客户端连接
|
||||||
func DeleteCli(id uint64) {
|
func DeleteCli(id uint64) {
|
||||||
for _, pool := range poolGroup.AllPool() {
|
for _, p := range poolGroup.AllPool() {
|
||||||
if pool.Stats().TotalConns == 0 {
|
if p.Stats().TotalConns == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conn, err := pool.Get(context.Background())
|
conn, err := p.Get(context.Background(), pool.WithNoUpdateLastActive())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -63,4 +64,6 @@ func DeleteCli(id uint64) {
|
|||||||
poolGroup.Close(conn.Info.AuthCertName)
|
poolGroup.Close(conn.Info.AuthCertName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 删除隧道
|
||||||
|
tunnelPoolGroup.Close(fmt.Sprintf("machine-tunnel-%d", id))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func init() {
|
|||||||
if v.Stats().TotalConns == 0 {
|
if v.Stats().TotalConns == 0 {
|
||||||
continue // 连接池中没有连接,跳过
|
continue // 连接池中没有连接,跳过
|
||||||
}
|
}
|
||||||
conn, err := v.Get(context.Background())
|
conn, err := v.Get(context.Background(), pool.WithNoUpdateLastActive())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // 获取连接失败,跳过
|
continue // 获取连接失败,跳过
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func init() {
|
|||||||
if v.Stats().TotalConns == 0 {
|
if v.Stats().TotalConns == 0 {
|
||||||
continue // 连接池中没有连接,跳过
|
continue // 连接池中没有连接,跳过
|
||||||
}
|
}
|
||||||
rc, err := v.Get(context.Background())
|
rc, err := v.Get(context.Background(), pool.WithNoUpdateLastActive())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // 获取连接失败,跳过
|
continue // 获取连接失败,跳过
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package pool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math/rand"
|
||||||
"mayfly-go/pkg/logx"
|
"mayfly-go/pkg/logx"
|
||||||
"mayfly-go/pkg/utils/stringx"
|
"mayfly-go/pkg/utils/stringx"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -45,20 +46,36 @@ func NewCachePool[T Conn](factory func() (T, error), opts ...Option[T]) *CachePo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get 获取连接(自动创建或复用缓存连接)
|
// Get 获取连接(自动创建或复用缓存连接)
|
||||||
func (p *CachePool[T]) Get(ctx context.Context) (T, error) {
|
func (p *CachePool[T]) Get(ctx context.Context, opts ...GetOption) (T, error) {
|
||||||
var zero T
|
var zero T
|
||||||
|
|
||||||
|
options := getOptions{updateLastActive: true} // 默认更新 lastActive
|
||||||
|
for _, apply := range opts {
|
||||||
|
apply(&options)
|
||||||
|
}
|
||||||
|
|
||||||
// 先尝试加读锁,仅用于查找可用连接
|
// 先尝试加读锁,仅用于查找可用连接
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
for _, entry := range p.cache {
|
if len(p.cache) >= p.config.MaxConns {
|
||||||
if time.Since(entry.lastActive) <= p.config.IdleTimeout {
|
keys := make([]string, 0, len(p.cache))
|
||||||
p.mu.RUnlock() // 找到后释放读锁
|
for k := range p.cache {
|
||||||
return entry.conn, nil
|
keys = append(keys, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
randomKey := keys[rand.Intn(len(keys))]
|
||||||
|
entry := p.cache[randomKey]
|
||||||
|
conn := entry.conn
|
||||||
|
|
||||||
|
if options.updateLastActive {
|
||||||
|
// 更新最后活跃时间
|
||||||
|
entry.lastActive = time.Now()
|
||||||
|
}
|
||||||
|
p.mu.RUnlock()
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
|
|
||||||
// 没有找到可用连接,升级为写锁进行清理和创建
|
// 没有找到可用连接,升级为写锁进行创建
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
@@ -66,14 +83,13 @@ func (p *CachePool[T]) Get(ctx context.Context) (T, error) {
|
|||||||
return zero, ErrPoolClosed
|
return zero, ErrPoolClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再次检查是否已有可用连接(防止并发创建)
|
// 再次检查是否已创建(防止并发)
|
||||||
for key, entry := range p.cache {
|
if len(p.cache) >= p.config.MaxConns {
|
||||||
if time.Since(entry.lastActive) <= p.config.IdleTimeout {
|
for _, entry := range p.cache {
|
||||||
|
if options.updateLastActive {
|
||||||
|
// 更新最后活跃时间
|
||||||
entry.lastActive = time.Now()
|
entry.lastActive = time.Now()
|
||||||
return entry.conn, nil
|
|
||||||
}
|
}
|
||||||
// 清理超时连接
|
|
||||||
if !p.closeConn(key, entry, false) {
|
|
||||||
return entry.conn, nil
|
return entry.conn, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +222,7 @@ func (p *CachePool[T]) cleanupIdle() {
|
|||||||
|
|
||||||
cutoff := time.Now().Add(-p.config.IdleTimeout)
|
cutoff := time.Now().Add(-p.config.IdleTimeout)
|
||||||
for key, entry := range p.cache {
|
for key, entry := range p.cache {
|
||||||
if entry.lastActive.Before(cutoff) || entry.conn.Ping() != nil {
|
if entry.lastActive.Before(cutoff) || !p.ping(entry.conn) {
|
||||||
logx.Infof("cache pool - cleaning up idle connection, key: %s", key)
|
logx.Infof("cache pool - cleaning up idle connection, key: %s", key)
|
||||||
// 如果连接超时或不可用,则关闭连接
|
// 如果连接超时或不可用,则关闭连接
|
||||||
p.closeConn(key, entry, false)
|
p.closeConn(key, entry, false)
|
||||||
@@ -214,6 +230,21 @@ func (p *CachePool[T]) cleanupIdle() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *CachePool[T]) ping(conn T) bool {
|
||||||
|
done := make(chan struct{})
|
||||||
|
var result bool
|
||||||
|
go func() {
|
||||||
|
result = conn.Ping() == nil
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return result
|
||||||
|
case <-time.After(2 * time.Second): // 设置超时
|
||||||
|
return false // 超时认为不可用
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *CachePool[T]) closeConn(key string, entry *cacheEntry[T], force bool) bool {
|
func (p *CachePool[T]) closeConn(key string, entry *cacheEntry[T], force bool) bool {
|
||||||
if !force {
|
if !force {
|
||||||
// 如果不是强制关闭且有连接关闭回调,则调用回调
|
// 如果不是强制关闭且有连接关闭回调,则调用回调
|
||||||
|
|||||||
@@ -74,12 +74,17 @@ func NewChannelPool[T Conn](factory func() (T, error), opts ...Option[T]) *ChanP
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ChanPool[T]) Get(ctx context.Context) (T, error) {
|
func (p *ChanPool[T]) Get(ctx context.Context, opts ...GetOption) (T, error) {
|
||||||
connChan := make(chan T, 1)
|
connChan := make(chan T, 1)
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
|
|
||||||
|
options := getOptions{updateLastActive: true} // 默认更新 lastActive
|
||||||
|
for _, apply := range opts {
|
||||||
|
apply(&options)
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
conn, err := p.get()
|
conn, err := p.get(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
} else {
|
} else {
|
||||||
@@ -108,7 +113,7 @@ func (p *ChanPool[T]) Get(ctx context.Context) (T, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ChanPool[T]) get() (T, error) {
|
func (p *ChanPool[T]) get(opts getOptions) (T, error) {
|
||||||
// 检查连接池是否已关闭
|
// 检查连接池是否已关闭
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
if p.closed {
|
if p.closed {
|
||||||
@@ -123,7 +128,9 @@ func (p *ChanPool[T]) get() (T, error) {
|
|||||||
case wrapper := <-p.idleConns:
|
case wrapper := <-p.idleConns:
|
||||||
atomic.AddInt32(&p.stats.IdleConns, -1)
|
atomic.AddInt32(&p.stats.IdleConns, -1)
|
||||||
atomic.AddInt32(&p.stats.ActiveConns, 1)
|
atomic.AddInt32(&p.stats.ActiveConns, 1)
|
||||||
|
if opts.updateLastActive {
|
||||||
wrapper.lastActive = time.Now()
|
wrapper.lastActive = time.Now()
|
||||||
|
}
|
||||||
return wrapper.conn, nil
|
return wrapper.conn, nil
|
||||||
default:
|
default:
|
||||||
return p.createConn()
|
return p.createConn()
|
||||||
|
|||||||
@@ -62,3 +62,20 @@ func WithOnConnClose[T Conn](fn func(conn T) error) Option[T] {
|
|||||||
c.OnConnClose = fn
|
c.OnConnClose = fn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**** GetOption Config ****/
|
||||||
|
|
||||||
|
// GetOption 用于配置 Get 的行为
|
||||||
|
type GetOption func(*getOptions)
|
||||||
|
|
||||||
|
// 控制 Get 行为的选项
|
||||||
|
type getOptions struct {
|
||||||
|
updateLastActive bool // 是否更新 lastActive,默认 true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNoUpdateLastActive 返回一个 Option,禁用更新 lastActive
|
||||||
|
func WithNoUpdateLastActive() GetOption {
|
||||||
|
return func(o *getOptions) {
|
||||||
|
o.updateLastActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type Conn interface {
|
|||||||
// Pool 连接池接口
|
// Pool 连接池接口
|
||||||
type Pool[T Conn] interface {
|
type Pool[T Conn] interface {
|
||||||
// 核心方法
|
// 核心方法
|
||||||
Get(ctx context.Context) (T, error)
|
Get(ctx context.Context, opts ...GetOption) (T, error)
|
||||||
Put(T) error
|
Put(T) error
|
||||||
Close()
|
Close()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user