worker

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

Documentation

Index

Constants

View Source
const (
	WakeupFanoutFull   = "full"
	WakeupFanoutGossip = "gossip"
)

Variables

View Source
var ErrInboundFull = errors.New("worker: no execution capacity for dispatched task")

ErrInboundFull is returned by SubmitDispatched when the worker has no free pool slot to run the task on (it is saturated). The dispatch handler treats this as a rejectable condition and rolls the claim back so the owner re-dispatches — which is exactly right, because the owner has already flipped the row to `running` by the time it asks the worker to take it.

View Source
var ErrWorkerNotAccepting = errors.New("worker: not accepting dispatched tasks")

ErrWorkerNotAccepting is returned by SubmitDispatched when the worker is not configured to accept dispatched tasks (WithInboundDispatch was never called).

Functions

func ParseNodeLabels

func ParseNodeLabels(raw string) map[string]string

func SubscribeWakeups

func SubscribeWakeups(ctx context.Context, bus event.Bus, extra ...<-chan struct{}) <-chan struct{}

func WakeupURLForNodeAddress

func WakeupURLForNodeAddress(nodeAddress string, apiPort int) (string, error)

Types

type ClaimInspector

type ClaimInspector interface {
	ClaimedTaskRunIDs(ctx context.Context, nodeID string, ids []uuid.UUID) ([]uuid.UUID, error)
}

ClaimInspector answers "of these task runs, which do I still hold the claim on?" without writing anything. Implemented by *run.Store; discovered from the LeaseRenewer by type assertion, the same way ExpiredReclaimer is discovered from the TaskClaimer, so a renewer that cannot answer simply falls back to the RenewLeases row count.

type Claimer

type Claimer struct {
	// contains filtered or unexported fields
}

func NewClaimer

func NewClaimer(nodeID string, store *run.Store, leaseTTL time.Duration, nodeLabels ...map[string]string) *Claimer

func (*Claimer) ClaimNext

func (c *Claimer) ClaimNext(ctx context.Context) (*models.TaskRun, error)

ClaimNext claims one ready task, or returns nil when no tasks are available.

func (*Claimer) ReclaimExpired

func (c *Claimer) ReclaimExpired(ctx context.Context) error

func (*Claimer) WithRateLimiter

func (c *Claimer) WithRateLimiter(l resourceLimiter) *Claimer

WithRateLimiter gates claimed tasks before they enter the worker pool.

type CompletionSink

type CompletionSink interface {
	// Succeeded finalizes a task that ran to a normal completion (whatever the
	// underlying container result was — the result string carries it).
	Succeeded(ctx context.Context, taskRun *models.TaskRun, result string, outputs map[string]string, branchSelections []string) error
	// Failed finalizes a task whose attempts were exhausted with an error.
	Failed(ctx context.Context, taskRun *models.TaskRun, failure error) error
	// Cached finalizes a task satisfied from the result cache.
	Cached(ctx context.Context, taskRun *models.TaskRun, source run.CacheHitSource, result string, outputs map[string]string, branchSelections []string) error
}

CompletionSink is the abstraction the runtime executor calls to finalize a task's terminal outcome. It exists so a single execution path can route its completion either to the local DB (the ClaimNext pull path, unchanged from Phase 1) or back to the owning node over /internal/complete (the run-owner push-dispatch path).

The three methods mirror the three store finalization calls the executor made directly before this abstraction was introduced:

Succeeded → run.Store.CompleteTaskClaimed
Failed    → run.Store.FailTaskClaimed
Cached    → run.Store.CacheHitTaskClaimed

The owner re-derives the real terminal status from the result string in CompleteTaskClaimed, so a "failure" result routed through Succeeded still lands as TaskStatusFailed on the owner — byte-identical to the local path.

func NewLocalSink

func NewLocalSink(store *run.Store) CompletionSink

NewLocalSink returns the default DB-backed completion sink.

type DistributedWakeupConfig

type DistributedWakeupConfig struct {
	Token      string
	FanoutMode string
	Signaler   *WakeupSignaler
	Resolver   WakeupPeerResolver
	HTTPClient *http.Client
}

type DistributedWakeups

type DistributedWakeups struct {
	// contains filtered or unexported fields
}

func NewDistributedWakeups

func NewDistributedWakeups(cfg DistributedWakeupConfig) *DistributedWakeups

func (*DistributedWakeups) HandleRemote

func (d *DistributedWakeups) HandleRemote(ctx context.Context, msg WakeupMessage)

func (*DistributedWakeups) Start

func (d *DistributedWakeups) Start(ctx context.Context, bus event.Bus) error

type ExpiredReclaimer

type ExpiredReclaimer interface {
	ReclaimExpired(ctx context.Context) error
}

type LeaseRenewer

type LeaseRenewer interface {
	RenewLeases(ctx context.Context, nodeID string, ids []uuid.UUID, newExpiresAt time.Time) (int64, error)
}

LeaseRenewer is implemented by any component that can issue a single batched UPDATE extending claim_expires_at for a set of in-flight task runs. Returns the number of rows actually updated; useful for both metric accuracy and detecting concurrent claim reassignment.

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool bounds concurrent goroutines using a semaphore.

The reservation half of the API (TryAcquire/Acquire → Go or Release) exists so a caller can prove it has capacity BEFORE it takes an action that is externally visible as "this task is running". Claiming a task flips its row to `running` in the catalog, so a claim issued without a slot advertises work the worker cannot start until some other task finishes — see Worker.Run and Worker.SubmitDispatched.

func NewPool

func NewPool(size int) *Pool

func (*Pool) Acquire

func (p *Pool) Acquire(ctx context.Context) error

Acquire reserves one execution slot, blocking until one frees or ctx is done.

func (*Pool) Free

func (p *Pool) Free() <-chan struct{}

Free is the wake-only signal poked whenever a slot is released. Select on it alongside a timer to wait for capacity without polling.

func (*Pool) Go

func (p *Pool) Go(fn func())

Go runs fn on a slot the caller has ALREADY reserved via TryAcquire/Acquire. The slot is released when fn returns.

func (*Pool) Release

func (p *Pool) Release()

Release returns a reserved slot that will not be used.

func (*Pool) Size

func (p *Pool) Size() int

Size reports the pool's maximum concurrency.

func (*Pool) Submit

func (p *Pool) Submit(ctx context.Context, fn func()) error

func (*Pool) TryAcquire

func (p *Pool) TryAcquire() bool

TryAcquire reserves one execution slot without blocking, reporting whether a slot was available. A successful reservation MUST be handed to Go (which consumes it) or returned with Release.

func (*Pool) Wait

func (p *Pool) Wait()

type ReclaimGate

type ReclaimGate interface {
	CanReclaim(ctx context.Context) (bool, error)
}

type ReclaimGateFunc

type ReclaimGateFunc func(context.Context) (bool, error)

func (ReclaimGateFunc) CanReclaim

func (f ReclaimGateFunc) CanReclaim(ctx context.Context) (bool, error)

type RunLeaseRenewer

type RunLeaseRenewer interface {
	// RenewOwnedLeases extends lease_expires_at in a single UPDATE for every
	// non-expired lease owned by ownerNode. Returns the number of rows
	// renewed, which is also the count of currently owned, non-expired runs.
	RenewOwnedLeases(ctx context.Context, ownerNode string, newExpiresAt time.Time) (int64, error)
}

RunLeaseRenewer is implemented by the run.LeaseStore and used by the worker's run-lease renewal goroutine.

type TaskClaimer

type TaskClaimer interface {
	ClaimNext(ctx context.Context) (*models.TaskRun, error)
}

type TaskExecutor

type TaskExecutor func(ctx context.Context, task *models.TaskRun)

func NewRuntimeExecutor

func NewRuntimeExecutor(store *run.Store, taskTimeout time.Duration, failurePolicy string, resolvers ...secret.Resolver) TaskExecutor

type WakeupMessage

type WakeupMessage struct {
	ID  string `json:"id,omitempty"`
	TTL int    `json:"ttl,omitempty"`
}

type WakeupPeerResolver

type WakeupPeerResolver interface {
	WakeupPeers(ctx context.Context) ([]string, error)
}

func NewCachedWakeupPeerResolver

func NewCachedWakeupPeerResolver(resolver WakeupPeerResolver, ttl time.Duration) WakeupPeerResolver

type WakeupPeerResolverFunc

type WakeupPeerResolverFunc func(context.Context) ([]string, error)

func (WakeupPeerResolverFunc) WakeupPeers

func (f WakeupPeerResolverFunc) WakeupPeers(ctx context.Context) ([]string, error)

type WakeupSignaler

type WakeupSignaler struct {
	// contains filtered or unexported fields
}

func NewWakeupSignaler

func NewWakeupSignaler() *WakeupSignaler

func (*WakeupSignaler) C

func (s *WakeupSignaler) C() <-chan struct{}

func (*WakeupSignaler) Signal

func (s *WakeupSignaler) Signal()

type Worker

type Worker struct {
	// contains filtered or unexported fields
}

func NewWorker

func NewWorker(claimer TaskClaimer, pool *Pool, pollInterval time.Duration, executor TaskExecutor) *Worker

func (*Worker) Run

func (w *Worker) Run(ctx context.Context) error

func (*Worker) SubmitDispatched

func (w *Worker) SubmitDispatched(d dispatch.InboundDispatch) error

SubmitDispatched enqueues a dispatched task for execution on the worker's shared pool. It is non-blocking: it returns ErrInboundFull when the worker has no free pool slot and ErrWorkerNotAccepting when the inbound path is disabled. *Worker implements dispatch.WorkerSubmitter via this method, so the dispatch handler can hand it accepted tasks directly (no adapter needed).

The non-blocking contract is deliberate: HandleDispatch runs on an HTTP request goroutine and must not block on a full pool. Saturation surfaces as a 409 the owner retries, rather than holding the dispatch RPC open.

A pool slot is RESERVED here, before the task is accepted, so acceptance and capacity are the same decision. The owner flips the row to `running` before it POSTs the dispatch and rolls that claim back when this method errors — so accepting a task the worker cannot start would leave the catalog claiming work is running while it sits parked behind another task's execution. The reservation travels with the task through the inbound channel and is consumed by drainInbound (Pool.Go), or handed back if the enqueue itself fails.

func (*Worker) WithInboundDispatch

func (w *Worker) WithInboundDispatch(completionToken string) *Worker

WithInboundDispatch enables the Phase 2 run-owner push path: the worker accepts dispatched tasks via SubmitDispatched and drains them onto the same execution pool as ClaimNext'd tasks. completionToken is the bearer token the worker presents when reporting a dispatched task's completion back to the owner's /internal/complete (the CAESIUM_INTERNAL_WAKEUP_TOKEN).

The buffer is sized to the pool size (floored at 1) because every buffered task already holds a reserved pool slot (SubmitDispatched reserves before it enqueues), so at most one pool's worth of tasks can ever be in the buffer. It is a hand-off queue between the HTTP goroutine and the Run loop, not a backlog: capacity — not buffer space — is what admits a dispatch. Without an explicit call this stays nil and the worker behaves byte-identically to Phase 1.

func (*Worker) WithLeaseRenewal

func (w *Worker) WithLeaseRenewal(renewer LeaseRenewer, leaseTTL, renewInterval time.Duration) *Worker

WithLeaseRenewal configures per-node batched lease renewal.

  • renewer issues a single UPDATE for all in-flight claims at once.
  • leaseTTL is the configured claim TTL; it drives the renewal cadence and the skip-when-not-needed threshold.
  • renewInterval is the override interval; pass 0 to use leaseTTL/4.

func (*Worker) WithReclaimGate

func (w *Worker) WithReclaimGate(gate ReclaimGate) *Worker

func (*Worker) WithReclaimInterval

func (w *Worker) WithReclaimInterval(interval time.Duration) *Worker

func (*Worker) WithRunLeaseRenewal

func (w *Worker) WithRunLeaseRenewal(renewer RunLeaseRenewer, leaseTTL time.Duration, nodeID string) *Worker

WithRunLeaseRenewal configures per-node batched run-lease renewal for Phase 2 run-owner mode. Piggybacked on the same ticker cadence as task claim renewals (leaseTTL/4). nodeID is the CAESIUM_NODE_ADDRESS value that identifies this node in run_leases.owner_node.

func (*Worker) WithWakeups

func (w *Worker) WithWakeups(ch <-chan struct{}) *Worker

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL