feat: add bounded task priority and fair application dispatch
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/dispatch"
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/jsonvalue"
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/jsonwire"
|
||||
@@ -25,19 +26,25 @@ const WorkerSubprotocol = "pulse.worker.v1"
|
||||
|
||||
// WorkerHub 管理节点内连接生命周期;任务归属和容量仍由 MySQL 决定。
|
||||
type WorkerHub struct {
|
||||
service *worker.Service
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
slots chan struct{}
|
||||
mu sync.Mutex
|
||||
closing bool
|
||||
wg sync.WaitGroup
|
||||
poll time.Duration
|
||||
service *worker.Service
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
slots chan struct{}
|
||||
mu sync.Mutex
|
||||
closing bool
|
||||
wg sync.WaitGroup
|
||||
poll time.Duration
|
||||
dispatch *dispatch.Pool
|
||||
emptyPolls map[[32]byte]time.Time
|
||||
}
|
||||
|
||||
func NewWorkerHub(parent context.Context, service *worker.Service) *WorkerHub {
|
||||
ctx, cancel := context.WithCancel(parent)
|
||||
return &WorkerHub{service: service, ctx: ctx, cancel: cancel, slots: make(chan struct{}, 128), poll: time.Second}
|
||||
h := &WorkerHub{service: service, ctx: ctx, cancel: cancel, slots: make(chan struct{}, 128), poll: time.Second, emptyPolls: make(map[[32]byte]time.Time)}
|
||||
h.dispatch = dispatch.New(ctx, 4, 128)
|
||||
h.wg.Add(1)
|
||||
go func() { defer h.wg.Done(); <-h.dispatch.Done() }()
|
||||
return h
|
||||
}
|
||||
func RegisterWorkers(server *http.Server, hub *WorkerHub) {
|
||||
server.Handler.(*http.ServeMux).Handle("GET /v1/worker/connect", hub)
|
||||
@@ -249,12 +256,53 @@ func (h *WorkerHub) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
eligible := false
|
||||
leases := map[core.ID]time.Time{}
|
||||
cancelSent := map[core.ID]bool{}
|
||||
results := make(chan claimResult, 1)
|
||||
pending, burstLeft := false, 0
|
||||
pollKey := workerPollKey(token.NamespaceID, input)
|
||||
canRequest := func() bool {
|
||||
return eligible && !draining && capacity > 0 && len(leases) < int(capacity) && len(outgoing) < 4
|
||||
}
|
||||
requestClaim := func() {
|
||||
if pending || burstLeft == 0 || !canRequest() {
|
||||
return
|
||||
}
|
||||
pending = h.dispatch.Submit(dispatch.Job{Context: ctx, Application: string(binding.ApplicationID), Queue: string(token.NamespaceID) + "/" + string(input.QueueID), Run: func(jobCtx context.Context) {
|
||||
results <- h.claimFair(jobCtx, token, binding, pollKey)
|
||||
}})
|
||||
if pending {
|
||||
burstLeft--
|
||||
}
|
||||
}
|
||||
ticker := time.NewTicker(h.poll)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case result := <-results:
|
||||
pending = false
|
||||
if result.err != nil {
|
||||
if fault.Code(result.err) == "STALE_SESSION" || fault.Code(result.err) == "UNAUTHENTICATED" || fault.Code(result.err) == "PERMISSION_DENIED" {
|
||||
send(workerFrame{Type: "error", Error: publicWorkerError(result.err)}, true)
|
||||
return
|
||||
}
|
||||
burstLeft = 0
|
||||
continue
|
||||
}
|
||||
if result.assignment == nil {
|
||||
burstLeft = 0
|
||||
continue
|
||||
}
|
||||
assignment := result.assignment
|
||||
raw, err := jsonwire.Marshal(assignment)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
leases[assignment.AttemptID] = assignment.LeaseUntil
|
||||
if !send(workerFrame{Type: "task.assign", Body: raw}, false) {
|
||||
return
|
||||
}
|
||||
requestClaim()
|
||||
case frame := <-incoming:
|
||||
call, stop := context.WithTimeout(ctx, 8*time.Second)
|
||||
var body any
|
||||
@@ -370,31 +418,9 @@ func (h *WorkerHub) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
delete(leases, attempt)
|
||||
}
|
||||
}
|
||||
if !eligible || draining || capacity == 0 || len(leases) >= int(capacity) || len(outgoing) >= 4 {
|
||||
continue
|
||||
}
|
||||
for burst := 0; burst < 4 && len(leases) < int(capacity) && len(outgoing) < 4; burst++ {
|
||||
call, stop := context.WithTimeout(ctx, 8*time.Second)
|
||||
assignment, err := h.service.Claim(call, token, binding)
|
||||
stop()
|
||||
if err != nil {
|
||||
if fault.Code(err) == "STALE_SESSION" || fault.Code(err) == "UNAUTHENTICATED" || fault.Code(err) == "PERMISSION_DENIED" {
|
||||
send(workerFrame{Type: "error", Error: publicWorkerError(err)}, true)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
if assignment == nil {
|
||||
break
|
||||
}
|
||||
raw, err := jsonwire.Marshal(assignment)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
leases[assignment.AttemptID] = assignment.LeaseUntil
|
||||
if !send(workerFrame{Type: "task.assign", Body: raw}, false) {
|
||||
return
|
||||
}
|
||||
if !pending {
|
||||
burstLeft = 4
|
||||
requestClaim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user