fix(gateway): 修复路由引擎P0问题
P0-07: RegisterStrategy添加互斥锁保护,解决并发注册策略时的数据竞争问题 P0-08: SelectProvider添加decision nil检查,避免nil指针被传递 使用TDD方法: 1. 编写测试验证问题存在 2. 修复代码 3. 测试验证通过
This commit is contained in:
@@ -3,6 +3,7 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"lijiaoqiao/gateway/internal/router/strategy"
|
"lijiaoqiao/gateway/internal/router/strategy"
|
||||||
)
|
)
|
||||||
@@ -18,6 +19,7 @@ type RoutingMetrics interface {
|
|||||||
|
|
||||||
// RoutingEngine 路由引擎
|
// RoutingEngine 路由引擎
|
||||||
type RoutingEngine struct {
|
type RoutingEngine struct {
|
||||||
|
mu sync.RWMutex
|
||||||
strategies map[string]strategy.StrategyTemplate
|
strategies map[string]strategy.StrategyTemplate
|
||||||
metrics RoutingMetrics
|
metrics RoutingMetrics
|
||||||
}
|
}
|
||||||
@@ -32,6 +34,8 @@ func NewRoutingEngine() *RoutingEngine {
|
|||||||
|
|
||||||
// RegisterStrategy 注册路由策略
|
// RegisterStrategy 注册路由策略
|
||||||
func (e *RoutingEngine) RegisterStrategy(name string, template strategy.StrategyTemplate) {
|
func (e *RoutingEngine) RegisterStrategy(name string, template strategy.StrategyTemplate) {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
e.strategies[name] = template
|
e.strategies[name] = template
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +58,11 @@ func (e *RoutingEngine) SelectProvider(ctx context.Context, req *strategy.Routin
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录指标
|
if decision == nil {
|
||||||
if e.metrics != nil && decision != nil {
|
return nil, ErrStrategyNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.metrics != nil {
|
||||||
e.metrics.RecordSelection(decision.Provider, decision.Strategy, decision)
|
e.metrics.RecordSelection(decision.Provider, decision.Strategy, decision)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,3 +152,88 @@ func (m *MockRoutingMetrics) RecordSelection(provider string, strategyName strin
|
|||||||
m.takeoverMark = decision.TakeoverMark
|
m.takeoverMark = decision.TakeoverMark
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== P0问题测试 ====================
|
||||||
|
|
||||||
|
// TestP0_07_RegisterStrategy_ThreadSafety 测试P0-07: 策略注册非线程安全
|
||||||
|
func TestP0_07_RegisterStrategy_ThreadSafety(t *testing.T) {
|
||||||
|
engine := NewRoutingEngine()
|
||||||
|
|
||||||
|
// 并发注册多个策略,启用-race检测器可以发现数据竞争
|
||||||
|
done := make(chan bool)
|
||||||
|
const goroutines = 100
|
||||||
|
|
||||||
|
for i := 0; i < goroutines; i++ {
|
||||||
|
go func(idx int) {
|
||||||
|
name := strategyName(idx)
|
||||||
|
tpl := strategy.NewCostBasedTemplate(name, strategy.CostParams{
|
||||||
|
MaxCostPer1KTokens: 1.0,
|
||||||
|
})
|
||||||
|
tpl.RegisterProvider("ProviderA", &MockProvider{
|
||||||
|
name: "ProviderA",
|
||||||
|
costPer1KTokens: 0.5,
|
||||||
|
available: true,
|
||||||
|
models: []string{"gpt-4"},
|
||||||
|
})
|
||||||
|
engine.RegisterStrategy(name, tpl)
|
||||||
|
done <- true
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有goroutine完成
|
||||||
|
for i := 0; i < goroutines; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证所有策略都已注册
|
||||||
|
for i := 0; i < goroutines; i++ {
|
||||||
|
name := strategyName(i)
|
||||||
|
_, ok := engine.strategies[name]
|
||||||
|
assert.True(t, ok, "Strategy %s should be registered", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func strategyName(idx int) string {
|
||||||
|
return "strategy_" + string(rune('a'+idx%26)) + string(rune('0'+idx/26%10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestP0_08_DecisionNilPanic 测试P0-08: decision可能为空指针
|
||||||
|
func TestP0_08_DecisionNilPanic(t *testing.T) {
|
||||||
|
engine := NewRoutingEngine()
|
||||||
|
|
||||||
|
// 创建一个返回nil decision但不返回错误的策略
|
||||||
|
nilDecisionStrategy := &NilDecisionStrategy{}
|
||||||
|
|
||||||
|
engine.RegisterStrategy("nil_decision", nilDecisionStrategy)
|
||||||
|
|
||||||
|
// 设置metrics
|
||||||
|
engine.metrics = &MockRoutingMetrics{}
|
||||||
|
|
||||||
|
req := &strategy.RoutingRequest{
|
||||||
|
Model: "gpt-4",
|
||||||
|
UserID: "user123",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证返回ErrStrategyNotFound而不是panic
|
||||||
|
decision, err := engine.SelectProvider(context.Background(), req, "nil_decision")
|
||||||
|
|
||||||
|
assert.Error(t, err, "Should return error when decision is nil")
|
||||||
|
assert.Equal(t, ErrStrategyNotFound, err, "Should return ErrStrategyNotFound")
|
||||||
|
assert.Nil(t, decision, "Decision should be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NilDecisionStrategy 返回nil decision的测试策略
|
||||||
|
type NilDecisionStrategy struct{}
|
||||||
|
|
||||||
|
func (s *NilDecisionStrategy) SelectProvider(ctx context.Context, req *strategy.RoutingRequest) (*strategy.RoutingDecision, error) {
|
||||||
|
// 返回nil decision但不返回错误 - 这模拟了潜在的边界情况
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NilDecisionStrategy) Name() string {
|
||||||
|
return "nil_decision"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NilDecisionStrategy) Type() string {
|
||||||
|
return "nil_decision"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user