Go 实现 /api/v1/analysis 契约、Demo/Live Worker、DeerFlow SSE;含设计文档与冒烟脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
65 lines
1.3 KiB
Go
65 lines
1.3 KiB
Go
package worker
|
|
|
|
import (
|
|
"log"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"git.gozeros.com/wsk/eightbf-analysis-go/internal/domain"
|
|
"git.gozeros.com/wsk/eightbf-analysis-go/internal/repo"
|
|
)
|
|
|
|
// LiveWorker 定时扫 queued,最多并发 2 场交给 LiveChainRunner。
|
|
type LiveWorker struct {
|
|
Repo *repo.JobRepo
|
|
Runner *LiveChainRunner
|
|
PollEvery time.Duration
|
|
inflight int32
|
|
}
|
|
|
|
func (w *LiveWorker) Start() {
|
|
if w.PollEvery <= 0 {
|
|
w.PollEvery = time.Second
|
|
}
|
|
go func() {
|
|
t := time.NewTicker(w.PollEvery)
|
|
defer t.Stop()
|
|
for range t.C {
|
|
w.poll()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (w *LiveWorker) poll() {
|
|
if atomic.LoadInt32(&w.inflight) >= 2 {
|
|
return
|
|
}
|
|
queued, err := w.Repo.FindQueuedJobs()
|
|
if err != nil {
|
|
log.Printf("LiveWorker 扫队列失败: %v", err)
|
|
return
|
|
}
|
|
for _, job := range queued {
|
|
if atomic.LoadInt32(&w.inflight) >= 2 {
|
|
break
|
|
}
|
|
job.Status = domain.StatusRunning
|
|
job.Phase = "claimed"
|
|
if err := w.Repo.UpdateProgress(job); err != nil {
|
|
log.Printf("LiveWorker 占坑失败 jobId=%s: %v", job.ID, err)
|
|
continue
|
|
}
|
|
atomic.AddInt32(&w.inflight, 1)
|
|
jobID := job.ID
|
|
go func() {
|
|
defer atomic.AddInt32(&w.inflight, -1)
|
|
fresh, err := w.Repo.FindByID(jobID)
|
|
if err != nil || fresh == nil {
|
|
return
|
|
}
|
|
log.Printf("开始真模型会商 jobId=%s", jobID)
|
|
w.Runner.Run(fresh)
|
|
}()
|
|
}
|
|
}
|