mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 00:10:25 +08:00 
			
		
		
		
	fix: 连接池修复
This commit is contained in:
		@@ -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?',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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}]缓存。 确认继续吗?',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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] {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user