feat: 为执行后端增加独立容量池
This commit is contained in:
@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
runnerv1 "gitea.dev/actionslib/runner/v1"
|
||||
)
|
||||
|
||||
@@ -19,10 +22,12 @@ type PollerConfig struct {
|
||||
Labels []string
|
||||
EmptyBackoff time.Duration
|
||||
ErrorBackoff time.Duration
|
||||
Capacity int
|
||||
}
|
||||
|
||||
// Poller is the scheduler component. Once Gitea assigns a task, it never
|
||||
// fetches another one until the current assignment is durably dispatched.
|
||||
// Poller is the scheduler component. Each fetcher keeps its assigned task
|
||||
// until that assignment is durably dispatched; all fetchers share one runner
|
||||
// declaration and a monotonic tasks version.
|
||||
type Poller struct {
|
||||
Client PollClient
|
||||
Scheduler *Scheduler
|
||||
@@ -49,9 +54,21 @@ func (p Poller) Run(ctx context.Context) error {
|
||||
if errorBackoff <= 0 {
|
||||
errorBackoff = 5 * time.Second
|
||||
}
|
||||
var tasksVersion int64
|
||||
capacity := p.Config.Capacity
|
||||
if capacity < 1 {
|
||||
capacity = 1
|
||||
}
|
||||
var tasksVersion atomic.Int64
|
||||
group, groupContext := errgroup.WithContext(ctx)
|
||||
for range capacity {
|
||||
group.Go(func() error { return p.runFetcher(groupContext, &tasksVersion, emptyBackoff, errorBackoff) })
|
||||
}
|
||||
return group.Wait()
|
||||
}
|
||||
|
||||
func (p Poller) runFetcher(ctx context.Context, tasksVersion *atomic.Int64, emptyBackoff, errorBackoff time.Duration) error {
|
||||
for {
|
||||
response, err := p.Client.FetchTask(ctx, tasksVersion)
|
||||
response, err := p.Client.FetchTask(ctx, tasksVersion.Load())
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
@@ -69,7 +86,7 @@ func (p Poller) Run(ctx context.Context) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
tasksVersion = response.GetTasksVersion()
|
||||
storeMaximum(tasksVersion, response.GetTasksVersion())
|
||||
task := response.GetTask()
|
||||
if task == nil {
|
||||
if !wait(ctx, emptyBackoff) {
|
||||
@@ -93,6 +110,14 @@ func (p Poller) Run(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func storeMaximum(value *atomic.Int64, candidate int64) {
|
||||
for current := value.Load(); candidate > current; current = value.Load() {
|
||||
if value.CompareAndSwap(current, candidate) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p Poller) report(err error) {
|
||||
if p.OnError != nil {
|
||||
p.OnError(err)
|
||||
|
||||
@@ -115,3 +115,52 @@ func TestPollerRetriesAssignedTaskBeforeFetchingAnother(t *testing.T) {
|
||||
t.Fatalf("dispatches=%d fetches-before-dispatch=%d declares=%d", dispatcher.calls, dispatcher.fetchesAtSuccess, client.declared)
|
||||
}
|
||||
}
|
||||
|
||||
type blockingPollClient struct {
|
||||
mu sync.Mutex
|
||||
declared int
|
||||
started chan struct{}
|
||||
}
|
||||
|
||||
func (c *blockingPollClient) Declare(context.Context, string, []string) error {
|
||||
c.mu.Lock()
|
||||
c.declared++
|
||||
c.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *blockingPollClient) FetchTask(ctx context.Context, _ int64) (*runnerv1.FetchTaskResponse, error) {
|
||||
c.started <- struct{}{}
|
||||
<-ctx.Done()
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
func TestPollerStartsConfiguredNumberOfFetchersAfterOneDeclare(t *testing.T) {
|
||||
client := &blockingPollClient{started: make(chan struct{}, 3)}
|
||||
poller := Poller{
|
||||
Client: client,
|
||||
Scheduler: &Scheduler{TrustDomain: "ddupan.top", Dispatcher: &retryDispatcher{done: make(chan struct{})}},
|
||||
Config: PollerConfig{
|
||||
Version: "dev", Labels: []string{"self-hosted:host", "pod:host", "vm:host"}, Capacity: 3,
|
||||
},
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
finished := make(chan error, 1)
|
||||
go func() { finished <- poller.Run(ctx) }()
|
||||
for range 3 {
|
||||
select {
|
||||
case <-client.started:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("configured fetchers did not start")
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
if err := <-finished; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
if client.declared != 1 {
|
||||
t.Fatalf("declares = %d", client.declared)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user