fix(worker): 将 Gemini 同步请求交由异步队列执行
将非流式 Gemini generateContent 改为 River Worker 执行,API 通过批量状态查询等待完成,避免高并发同步请求耗尽 API 数据库连接池。\n\n生成图片在持久化时保存带哈希的资产引用,响应恢复时下载并校验后重建 Base64,数据库不保存媒体原文。请求体物化增加前置内存门禁,并扩展双 API/Worker PostgreSQL 压力测试。\n\n验证:go test ./... -count=1;go vet ./...;pnpm openapi;迁移安全检查;64 与 256 请求双角色异步 Gemini 压力测试。
This commit is contained in:
@@ -12,6 +12,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -39,6 +41,59 @@ func TestRequestAssetFromValueDetectsDataURLAndRawBase64(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaRequestBodySlotLimitsPreAuthWorkAndCanReleaseEarly(t *testing.T) {
|
||||
server := &Server{mediaRequestBodySlots: make(chan struct{}, 2)}
|
||||
var critical atomic.Int64
|
||||
var maxCritical atomic.Int64
|
||||
var afterRelease atomic.Int64
|
||||
var maxAfterRelease atomic.Int64
|
||||
updateMax := func(target *atomic.Int64, value int64) {
|
||||
for {
|
||||
current := target.Load()
|
||||
if value <= current || target.CompareAndSwap(current, value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
handler := server.withMediaRequestBodySlot(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
release, err := server.requestMediaBodyRelease(r.Context())
|
||||
if err != nil {
|
||||
t.Errorf("request media body release: %v", err)
|
||||
return
|
||||
}
|
||||
current := critical.Add(1)
|
||||
updateMax(&maxCritical, current)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
critical.Add(-1)
|
||||
release()
|
||||
current = afterRelease.Add(1)
|
||||
updateMax(&maxAfterRelease, current)
|
||||
time.Sleep(40 * time.Millisecond)
|
||||
afterRelease.Add(-1)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
var wait sync.WaitGroup
|
||||
for index := 0; index < 10; index++ {
|
||||
wait.Add(1)
|
||||
go func() {
|
||||
defer wait.Done()
|
||||
request := httptest.NewRequest(http.MethodPost, "/v1beta/models/test:generateContent", nil)
|
||||
handler.ServeHTTP(httptest.NewRecorder(), request)
|
||||
}()
|
||||
}
|
||||
wait.Wait()
|
||||
if maxCritical.Load() != 2 {
|
||||
t.Fatalf("pre-auth critical concurrency=%d, want 2", maxCritical.Load())
|
||||
}
|
||||
if maxAfterRelease.Load() <= 2 {
|
||||
t.Fatalf("early release did not admit later requests, post-release concurrency=%d", maxAfterRelease.Load())
|
||||
}
|
||||
if len(server.mediaRequestBodySlots) != 0 {
|
||||
t.Fatalf("media request body slots leaked: %d", len(server.mediaRequestBodySlots))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestModelNameSupportsObjectModelReference(t *testing.T) {
|
||||
got := requestModelName(map[string]any{
|
||||
"model": map[string]any{
|
||||
|
||||
Reference in New Issue
Block a user