dispatcher

package
v0.9.10 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package dispatcher provides a workqueue dispatcher that dequeues keys and invokes a callback for each one.

The dispatcher handles orphaned in-progress keys, concurrency limits, and batch sizing. Use Handle for synchronous dispatch or HandleAsync for non-blocking dispatch with a Future to await results.

Error Handling

When a callback returns an error, the dispatcher requeues, dead-letters, or drops the key depending on the error type and retry budget. To emit these errors as CloudEvents, pass WithErrorIngressURI:

handler := dispatcher.Handler(wq, 10, 5, callback, 3,
    dispatcher.WithErrorIngressURI(ctx, ingressURI, "my-wq"),
)

When the ingress URL is empty the option is a no-op, making the feature entirely opt-in.

Failure-Retry Backoff

By default a failed-and-requeued key reuses the workqueue's existing backoff. To install a custom failure-retry backoff (for example decorrelated exponential jitter), pass WithBackoff:

handler := dispatcher.Handler(wq, 10, 5, callback, 3,
    dispatcher.WithBackoff(func(attempts int) time.Duration {
        return backoff(attempts)
    }),
)

The hook is called with the key's current attempt count on each requeued failure. A positive return value defers the retry by that duration while preserving the attempt count, so the dead-letter cutoff stays reachable. A nil hook or a non-positive return falls back to the default bare requeue, making the option entirely opt-in.

Index

Examples

Constants

View Source
const (

	// ErrorEventType is the CloudEvent type for dispatcher error events.
	ErrorEventType = "dev.chainguard.workqueue.error.v1"
)

Variables

This section is empty.

Functions

func Handle

func Handle(ctx context.Context, wq workqueue.Interface, concurrency, batchSize int, f Callback, opts ...Option) error

Handle is a synchronous form of HandleAsync.

Example

ExampleHandle demonstrates dispatching a single round of work from a workqueue using Handle.

package main

import (
	"context"
	"fmt"

	"chainguard.dev/driftlessaf/workqueue"
	"chainguard.dev/driftlessaf/workqueue/dispatcher"
	"chainguard.dev/driftlessaf/workqueue/inmem"
)

func main() {
	wq := inmem.NewWorkQueue(5)
	ctx := context.Background()

	if err := wq.Queue(ctx, "example-key", workqueue.Options{}); err != nil {
		panic(err)
	}

	processed := false
	err := dispatcher.Handle(ctx, wq, 5, 0, func(_ context.Context, key string, _ workqueue.Options) error {
		fmt.Printf("Processing key: %s\n", key)
		processed = true
		return nil
	})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Processed: %v\n", processed)
}
Output:
Processing key: example-key
Processed: true

func Handler

func Handler(wq workqueue.Interface, concurrency, batchSize int, f Callback, maxRetry int, opts ...Option) http.Handler

Types

type Callback

type Callback func(ctx context.Context, key string, opts workqueue.Options) error

Callback is the function that Handle calls to process a particular key.

func ServiceCallback

func ServiceCallback(client workqueue.WorkqueueServiceClient) Callback

ServiceCallback returns a Callback that invokes the given service.

Example

ExampleServiceCallback demonstrates creating a Callback that delegates to a WorkqueueServiceClient.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/workqueue"
	"chainguard.dev/driftlessaf/workqueue/dispatcher"
)

func main() {
	// ServiceCallback wraps a gRPC WorkqueueServiceClient as a dispatcher Callback.
	// In production, pass a real client obtained from a gRPC connection.
	var client workqueue.WorkqueueServiceClient
	cb := dispatcher.ServiceCallback(client)
	_ = cb
	fmt.Println("ServiceCallback created")
}
Output:
ServiceCallback created

type DispatcherErrorEvent added in v0.4.0

type DispatcherErrorEvent struct {
	// Key is the workqueue key that failed.
	Key string `json:"key"`

	// Error is the error message.
	Error string `json:"error"`

	// Attempts is the number of times the key has been attempted.
	Attempts int `json:"attempts"`

	// Action describes the disposition (requeued, dead-lettered, dropped).
	Action string `json:"action"`

	// NonRetriableReason is set when Action is "dropped", providing the
	// reason the error was marked non-retriable.
	NonRetriableReason string `json:"nonRetriableReason,omitempty"`

	// OccurAt is the time the error occurred. This field is used as the
	// BigQuery partition field.
	OccurAt time.Time `json:"occurAt"`

	// ReconcilerName is the name of the reconciler / workqueue that produced
	// this error. It allows distinguishing errors from different reconcilers
	// that share the same key space.
	ReconcilerName string `json:"reconcilerName,omitempty"`
}

DispatcherErrorEvent is the CloudEvent payload for dispatcher errors.

type ErrorAction added in v0.4.0

type ErrorAction int

ErrorAction describes the disposition of a key after a dispatch error.

const (
	// ErrorRequeued indicates the key was returned to the queue for retry.
	ErrorRequeued ErrorAction = iota

	// ErrorDeadLettered indicates the key exhausted its retry budget and was
	// moved to the dead-letter queue.
	ErrorDeadLettered

	// ErrorDropped indicates the error was marked non-retriable and the key
	// was completed without further processing.
	ErrorDropped
)

func (ErrorAction) String added in v0.4.0

func (a ErrorAction) String() string

type ErrorContext added in v0.4.0

type ErrorContext struct {
	// Key is the workqueue key that failed.
	Key string

	// Err is the error returned by the callback.
	Err error

	// Attempts is the number of times the key has been attempted (including
	// the current attempt).
	Attempts int

	// Action is what the dispatcher did with the key after the error.
	Action ErrorAction

	// NonRetriableReason is set when Action is ErrorDropped, providing
	// the reason the error was marked non-retriable.
	NonRetriableReason string
}

ErrorContext carries information about a dispatch error.

type Future

type Future func() error

Future is a function that can be used to block on the result of a round of dispatching work.

func HandleAsync

func HandleAsync(ctx context.Context, wq workqueue.Interface, concurrency, batchSize int, f Callback, maxRetry int, opts ...Option) Future

HandleAsync initiates a single iteration of the dispatcher, possibly invoking the callback for several different keys. It returns a future that can be used to block on the result.

type Option added in v0.4.0

type Option func(*config)

Option configures optional dispatcher behavior.

func WithBackoff added in v0.7.14

func WithBackoff(fn func(attempts int) time.Duration) Option

WithBackoff sets the failure-retry backoff for the dispatcher. On each callback failure that is requeued (not dead-lettered, not dropped as non-retriable), the dispatcher calls fn with the key's current attempt count and, when fn returns a positive duration, requeues the key with that not-before delay while preserving the attempt count (so the dead-letter cutoff stays reachable).

When fn is nil or returns a non-positive duration, the dispatcher falls back to a bare requeue, identical to the behavior with this option unset. This makes the option entirely opt-in: an unconfigured dispatcher keeps the existing requeue behavior bit-for-bit. Use it to install decorrelated exponential jitter or any other attempt-driven backoff curve.

Example

ExampleWithBackoff demonstrates installing a failure-retry backoff. The hook receives the key's attempt count and returns the delay before the next retry; here it grows linearly with attempts. Returning a non-positive duration (or leaving the option unset) keeps the default bare-requeue behavior.

package main

import (
	"context"
	"fmt"
	"time"

	"chainguard.dev/driftlessaf/workqueue"
	"chainguard.dev/driftlessaf/workqueue/dispatcher"
	"chainguard.dev/driftlessaf/workqueue/inmem"
)

func main() {
	wq := inmem.NewWorkQueue(5)
	ctx := context.Background()

	if err := wq.Queue(ctx, "example-key", workqueue.Options{}); err != nil {
		panic(err)
	}

	// Back off two seconds per attempt before the next retry.
	backoff := func(attempts int) time.Duration {
		return time.Duration(attempts) * 2 * time.Second
	}

	err := dispatcher.Handle(ctx, wq, 5, 0, func(_ context.Context, _ string, _ workqueue.Options) error {
		return fmt.Errorf("something went wrong")
	}, dispatcher.WithBackoff(backoff))
	if err != nil {
		panic(err)
	}
	fmt.Println("dispatched with failure-retry backoff")
}
Output:
dispatched with failure-retry backoff

func WithDispatchPeriod added in v0.9.4

func WithDispatchPeriod(d time.Duration) Option

WithDispatchPeriod sets the minimum interval between dispatch passes for a Handler: at most one pass is admitted per period, and triggers beyond that are acknowledged and dropped. The default is one second. It has no effect on Handle or HandleAsync, which initiate unconditionally.

func WithErrorIngressURI added in v0.4.1

func WithErrorIngressURI(ctx context.Context, ingressURI, workqueueName string) Option

WithErrorIngressURI configures the dispatcher to emit errors as CloudEvents to the given broker URL. workqueueName is used as the CloudEvent source. When ingressURI is empty, error events are disabled (no-op).

The CloudEvents client is constructed eagerly so it is shared across all dispatch rounds. If construction fails the option degrades gracefully to a no-op with a warning log.

Example

ExampleWithErrorIngressURI demonstrates enabling error events. When the broker URL is empty, error events are silently disabled.

package main

import (
	"context"
	"fmt"

	"chainguard.dev/driftlessaf/workqueue"
	"chainguard.dev/driftlessaf/workqueue/dispatcher"
	"chainguard.dev/driftlessaf/workqueue/inmem"
)

func main() {
	wq := inmem.NewWorkQueue(5)
	ctx := context.Background()

	if err := wq.Queue(ctx, "example-key", workqueue.Options{}); err != nil {
		panic(err)
	}

	// Empty URL disables error events (no-op).
	err := dispatcher.Handle(ctx, wq, 5, 0, func(_ context.Context, _ string, _ workqueue.Options) error {
		return fmt.Errorf("something went wrong")
	}, dispatcher.WithErrorIngressURI(ctx, "", "my-workqueue"))
	if err != nil {
		panic(err)
	}
	fmt.Println("dispatched with error events disabled")
}
Output:
dispatched with error events disabled

Jump to

Keyboard shortcuts

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