!136 fix: 连接池修复

Merge pull request !136 from zongyangleo/dev_0529
This commit is contained in:
Coder慌
2025-05-29 04:39:31 +00:00
committed by Gitee
6 changed files with 99 additions and 21 deletions

View File

@@ -59,7 +59,6 @@ export default {
docJsonError: 'Document JSON Format Error', docJsonError: 'Document JSON Format Error',
sortParams: 'Sort', sortParams: 'Sort',
otherParams: 'Other', otherParams: 'Other',
previewParams: 'Preview',
closeIndexConfirm: 'This operation will close index [{name}]. Do you want to continue?', closeIndexConfirm: 'This operation will close index [{name}]. Do you want to continue?',
openIndexConfirm: 'This operation will open index [{name}]. Do you want to continue?', openIndexConfirm: 'This operation will open index [{name}]. Do you want to continue?',
clearCacheConfirm: 'This operation will clear index [{name}] cache. Do you want to continue?', clearCacheConfirm: 'This operation will clear index [{name}] cache. Do you want to continue?',

View File

@@ -58,7 +58,6 @@ export default {
docJsonError: '文档JSON格式错误', docJsonError: '文档JSON格式错误',
sortParams: '排序', sortParams: '排序',
otherParams: '其他', otherParams: '其他',
previewParams: '预览',
closeIndexConfirm: '将会关闭索引:[{name}]。 确认继续吗?', closeIndexConfirm: '将会关闭索引:[{name}]。 确认继续吗?',
openIndexConfirm: '将会打开索引:[{name}]。 确认继续吗?', openIndexConfirm: '将会打开索引:[{name}]。 确认继续吗?',
clearCacheConfirm: '将会清除索引:[{name}]缓存。 确认继续吗?', clearCacheConfirm: '将会清除索引:[{name}]缓存。 确认继续吗?',

View File

@@ -116,7 +116,6 @@
<template #footer> <template #footer>
<div> <div>
<el-button size="small" @click="onPreviewParam" icon="view">{{ t('es.previewParams') }}</el-button>
<el-button size="small" @click="onClearParam" icon="refresh">{{ t('common.reset') }}</el-button> <el-button size="small" @click="onClearParam" icon="refresh">{{ t('common.reset') }}</el-button>
<!-- <el-button size="small" @click="onSaveParam" type="primary" icon="check">{{ t('common.save') }}</el-button>--> <!-- <el-button size="small" @click="onSaveParam" type="primary" icon="check">{{ t('common.save') }}</el-button>-->
@@ -472,7 +471,7 @@ const onSaveParam = () => {
// 保存查询条件 // 保存查询条件
}; };
const onPreviewParam = () => { const onSearch = () => {
parseParams(); parseParams();
MonacoEditorBox({ MonacoEditorBox({
content: JSON.stringify(state.search, null, 2), content: JSON.stringify(state.search, null, 2),
@@ -480,7 +479,10 @@ const onPreviewParam = () => {
language: 'json', language: 'json',
width: state.searchBoxWidth, width: state.searchBoxWidth,
canChangeLang: false, canChangeLang: false,
options: { wordWrap: 'on', tabSize: 2, readOnly: true }, // 自动换行 options: { wordWrap: 'on', tabSize: 2, readOnly: false }, // 自动换行
confirmFn: (val: string) => {
emit('search', JSON.parse(val));
},
}); });
}; };
@@ -572,11 +574,6 @@ const parseParams = () => {
delete state.search['minimum_should_match']; delete state.search['minimum_should_match'];
} }
}; };
const onSearch = () => {
parseParams();
emit('search', state.search);
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -73,7 +73,7 @@ func (app *instanceAppImpl) GetPageList(condition *entity.InstanceQuery, orderBy
} }
func (app *instanceAppImpl) DoConn(ctx context.Context, instanceId uint64, fn func(*esi.EsConn) error) error { func (app *instanceAppImpl) DoConn(ctx context.Context, instanceId uint64, fn func(*esi.EsConn) error) error {
p, err := poolGroup.GetChanPool(fmt.Sprintf("es-%d", instanceId), func() (*esi.EsConn, error) { p, err := poolGroup.GetCachePool(fmt.Sprintf("es-%d", instanceId), func() (*esi.EsConn, error) {
return app.createConn(context.Background(), instanceId) return app.createConn(context.Background(), instanceId)
}) })

View File

@@ -1,8 +1,11 @@
package pool package pool
import ( import (
"fmt"
"mayfly-go/pkg/logx" "mayfly-go/pkg/logx"
"runtime"
"sync" "sync"
"time"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
@@ -11,12 +14,16 @@ type PoolGroup[T Conn] struct {
mu sync.RWMutex mu sync.RWMutex
poolGroup map[string]Pool[T] poolGroup map[string]Pool[T]
createGroup singleflight.Group createGroup singleflight.Group
closingWg sync.WaitGroup
closingMu sync.Mutex
closingCh chan struct{} // 添加关闭通道
} }
func NewPoolGroup[T Conn]() *PoolGroup[T] { func NewPoolGroup[T Conn]() *PoolGroup[T] {
return &PoolGroup[T]{ return &PoolGroup[T]{
poolGroup: make(map[string]Pool[T]), poolGroup: make(map[string]Pool[T]),
createGroup: singleflight.Group{}, createGroup: singleflight.Group{},
closingCh: make(chan struct{}),
} }
} }
@@ -86,28 +93,92 @@ func (pg *PoolGroup[T]) Get(key string) (Pool[T], bool) {
return nil, false return nil, false
} }
// 添加一个异步关闭的辅助函数
func (pg *PoolGroup[T]) asyncClose(pool Pool[T], key string) {
pg.closingMu.Lock()
pg.closingWg.Add(1)
pg.closingMu.Unlock()
go func() {
defer func() {
pg.closingMu.Lock()
pg.closingWg.Done()
pg.closingMu.Unlock()
}()
// 设置超时检测
done := make(chan struct{})
go func() {
pool.Close()
close(done)
}()
// 等待关闭完成或超时
select {
case <-done:
logx.Infof("pool group - pool closed successfully, key: %s", key)
case <-time.After(5 * time.Second):
logx.Errorf("pool group - pool close timeout, possible deadlock detected, key: %s", key)
// 打印当前 goroutine 的堆栈信息
buf := make([]byte, 1<<16)
runtime.Stack(buf, true)
logx.Errorf("pool group - goroutine stack trace:\n%s", buf)
}
}()
}
func (pg *PoolGroup[T]) Close(key string) error { func (pg *PoolGroup[T]) Close(key string) error {
pg.mu.Lock() pg.mu.Lock()
defer pg.mu.Unlock()
if p, ok := pg.poolGroup[key]; ok { if p, ok := pg.poolGroup[key]; ok {
logx.Infof("pool group - close pool, key: %s", key) logx.Infof("pool group - closing pool, key: %s", key)
p.Close()
pg.createGroup.Forget(key) pg.createGroup.Forget(key)
delete(pg.poolGroup, key) delete(pg.poolGroup, key)
pg.mu.Unlock()
pg.asyncClose(p, key)
return nil
} }
pg.mu.Unlock()
return nil return nil
} }
func (pg *PoolGroup[T]) CloseAll() { func (pg *PoolGroup[T]) CloseAll() {
pg.mu.Lock() pg.mu.Lock()
defer pg.mu.Unlock() pools := make(map[string]Pool[T], len(pg.poolGroup))
for k, v := range pg.poolGroup {
for key := range pg.poolGroup { pools[k] = v
pg.poolGroup[key].Close()
pg.createGroup.Forget(key)
} }
pg.poolGroup = make(map[string]Pool[T]) pg.poolGroup = make(map[string]Pool[T])
pg.mu.Unlock()
// 异步关闭所有池
for key, pool := range pools {
pg.asyncClose(pool, key)
}
}
// 添加一个用于监控连接池关闭状态的方法
func (pg *PoolGroup[T]) WaitForClose(timeout time.Duration) error {
// 创建一个新的通道用于通知等待完成
done := make(chan struct{})
// 启动一个 goroutine 来等待所有关闭操作完成
go func() {
pg.closingWg.Wait()
close(done)
}()
// 等待完成或超时
select {
case <-done:
return nil
case <-time.After(timeout):
// 在超时时打印当前状态
pg.mu.RLock()
remainingPools := len(pg.poolGroup)
pg.mu.RUnlock()
logx.Errorf("pool group - close timeout, remaining pools: %d", remainingPools)
return fmt.Errorf("wait for pool group close timeout after %v", timeout)
}
} }
func (pg *PoolGroup[T]) AllPool() map[string]Pool[T] { func (pg *PoolGroup[T]) AllPool() map[string]Pool[T] {

View File

@@ -183,11 +183,11 @@ func TestCachePool_Basic(t *testing.T) {
ctx := context.Background() ctx := context.Background()
conn1, _ := pool.Get(ctx) conn1, _ := pool.Get(ctx)
_ = pool.Put(conn1)
conn2, _ := pool.Get(ctx) conn2, _ := pool.Get(ctx)
if conn1 != conn2 { if conn1 != conn2 {
t.Fatal("缓存池应复用同一连接") t.Fatal("缓存池应复用同一连接")
} }
_ = pool.Put(conn1)
_ = pool.Put(conn2) _ = pool.Put(conn2)
pool.Close() pool.Close()
} }
@@ -564,6 +564,12 @@ func TestPoolGroup_ConcurrentAccess(t *testing.T) {
} }
wg.Wait() wg.Wait()
// 等待所有池关闭完成
err := group.WaitForClose(10 * time.Second)
if err != nil {
t.Errorf("等待池关闭超时: %v", err)
}
// 验证所有池都已关闭 // 验证所有池都已关闭
pools = group.AllPool() pools = group.AllPool()
if len(pools) != 0 { if len(pools) != 0 {
@@ -597,6 +603,12 @@ func TestPoolGroup_ConcurrentClose(t *testing.T) {
} }
wg.Wait() wg.Wait()
// 等待所有池关闭完成
err := group.WaitForClose(10 * time.Second)
if err != nil {
t.Errorf("等待池关闭超时: %v", err)
}
// 验证所有池都已关闭 // 验证所有池都已关闭
pools := group.AllPool() pools := group.AllPool()
if len(pools) != 0 { if len(pools) != 0 {