source

package
v2.10.2 Latest Latest
Warning

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

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

Documentation

Overview

Package source provides built-in partition source implementations.

Partition sources discover available partitions for assignment. The package includes:

  • Static: Fixed list of partitions defined at compile time.
  • NatsKV: Dynamic partition list backed by a NATS KeyValue bucket, supporting live updates via KV watches (implements PartitionSource, PartitionUpdater, and WatchablePartitionSource).

Custom sources can be implemented by satisfying the types.PartitionSource interface.

Index

Constants

This section is empty.

Variables

View Source
var ErrUpdateRetryExhausted = errors.New("update retry budget exhausted")

ErrUpdateRetryExhausted is returned by Update and Modify when all CAS retry attempts are exhausted due to persistent concurrent conflicts.

Functions

This section is empty.

Types

type NatsKV

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

NatsKV implements a partition source backed by a NATS KeyValue bucket.

It watches a specific key in the KV bucket for updates to the partition list, supplemented by a periodic reconcile loop that recovers from missed watcher events. The combination of watch + reconcile guarantees eventual convergence with KV state.

Writes use CAS (compare-and-swap) to prevent silent lost updates from concurrent callers. Use Modify, AddPartitions, or RemovePartitions for read-modify-write operations; Update is a CAS-fenced authoritative replace.

The revision and known fields enable downstream audit logic to distinguish a never-written source (known=false) from a written-then-deleted source (known=true, empty partitions), which have different implications for coverage invariants.

func NewNatsKV

func NewNatsKV(kv jetstream.KeyValue, key string, logger types.Logger, opts ...NatsKVOption) *NatsKV

NewNatsKV creates a new NATS KV-based partition source.

Existing callers that omit opts receive a 30-second reconcile interval by default. Pass WithReconcileInterval(0) to disable polling (useful in tests that want deterministic behaviour).

Parameters:

  • kv: The NATS KeyValue bucket to use
  • key: The key where partitions are stored (JSON or gzip-compressed JSON)
  • logger: Optional structured logger (may be nil)
  • opts: Functional options

Returns:

  • *NatsKV: Configured source (call Start before use)

func (*NatsKV) AddPartitions added in v2.4.0

func (s *NatsKV) AddPartitions(ctx context.Context, partitions ...types.Partition) error

AddPartitions adds one or more partitions to the source, preserving concurrent additions. Duplicate partitions (by CanonicalID) are silently ignored; calling AddPartitions twice with the same partition is a no-op, not an error.

Internally implemented via Modify, so it is safe for concurrent callers.

Parameters:

  • ctx: Context for the operation
  • partitions: Partitions to add (validated before any KV round-trip)

Returns:

  • error: Validation error, or ErrUpdateRetryExhausted if CAS budget exhausted

func (*NatsKV) List

func (s *NatsKV) List(_ context.Context) ([]types.Partition, error)

List returns the current list of partitions (deep copy).

Returns:

  • []types.Partition: Current partition list
  • error: Always nil

func (*NatsKV) Modify added in v2.4.0

func (s *NatsKV) Modify(ctx context.Context, fn func([]types.Partition) []types.Partition) error

Modify atomically transforms the partition list by applying fn, retrying on concurrent writes until the CAS succeeds or the retry budget is exhausted.

fn receives a fresh snapshot read directly from KV (not the local cache) on every attempt and must be deterministic and side-effect-free — it may be called multiple times. The function signature is:

fn(current []types.Partition) []types.Partition

On ErrKeyNotFound (key never written) fn receives an empty list. Returns ErrUpdateRetryExhausted if all attempts fail.

Example:

err := src.Modify(ctx, func(current []types.Partition) []types.Partition {
    return append(current, types.Partition{Keys: []string{"new"}})
})

Parameters:

  • ctx: Context for the operation
  • fn: Transform function (may be called multiple times; must be side-effect-free)

Returns:

  • error: Modify error or ErrUpdateRetryExhausted

func (*NatsKV) RemovePartitions added in v2.4.0

func (s *NatsKV) RemovePartitions(ctx context.Context, partitions ...types.Partition) error

RemovePartitions removes one or more partitions from the source, matching by CanonicalID. Partitions not found are silently ignored. Concurrent mutations are preserved; internally implemented via Modify.

Parameters:

  • ctx: Context for the operation
  • partitions: Partitions to remove (validated before any KV round-trip)

Returns:

  • error: Validation error, or ErrUpdateRetryExhausted if CAS budget exhausted

func (*NatsKV) Snapshot added in v2.4.0

func (s *NatsKV) Snapshot(_ context.Context) ([]types.Partition, uint64, bool, error)

Snapshot returns the current partition list together with the last observed KV revision and the known flag. This implements types.RevisionedPartitionSource.

The known flag distinguishes a never-written source (known=false, revision=0) from a written-then-deleted source (known=true, revision=deleteRevision, empty partitions).

Parameters:

  • ctx: Context for cancellation (unused; snapshot is served from memory)

Returns:

  • partitions: Deep copy of current partition list
  • revision: Last observed KV revision
  • known: True once any KV event has been observed
  • error: Always nil

func (*NatsKV) Start

func (s *NatsKV) Start(ctx context.Context) error

Start initializes the source and starts watching for updates.

It fetches the initial partition list from KV, seeds revision and known state, and spawns background watcher and reconcile goroutines.

Returns:

  • error: Initialization error (invalid initial data, watcher setup failure)

func (*NatsKV) Stop

func (s *NatsKV) Stop(_ context.Context) error

Stop stops the watcher and reconcile loop and waits for all source-owned goroutines to exit.

Returns:

  • error: Cleanup error (nil on success)

func (*NatsKV) Update

func (s *NatsKV) Update(ctx context.Context, partitions []types.Partition) error

Update replaces the entire partition list in the KV bucket using CAS.

Update is an authoritative-replace primitive: it replaces whatever is currently in KV with exactly the provided list. It is NOT safe for concurrent read-modify-write patterns — use Modify for those. Two callers issuing Update with different lists will serialize through CAS retry; last-writer-wins semantics apply.

On success, the local cache is updated immediately so a subsequent List() returns the new value without waiting for the watcher round-trip.

Returns ErrUpdateRetryExhausted if all CAS retries are exhausted.

Note: NATS KV buckets have a default value size limit of 1MB (MaxMsgSize). Large partition lists are automatically compressed with Gzip. If the compressed size still exceeds the limit, the update will fail.

Parameters:

  • ctx: Context for the operation
  • partitions: New partition list (replaces existing content)

Returns:

  • error: Update error or ErrUpdateRetryExhausted

func (*NatsKV) Watch

func (s *NatsKV) Watch(ctx context.Context) <-chan struct{}

Watch returns a channel that emits a signal when the partition list changes.

The channel is buffered (capacity 1). If the caller is not keeping up, signals are dropped rather than blocking the source. The channel is closed when ctx is cancelled.

Parameters:

  • ctx: Context whose cancellation deregisters the watcher

Returns:

  • <-chan struct{}: Signal channel

type NatsKVOption added in v2.4.0

type NatsKVOption func(*NatsKV)

NatsKVOption is a functional option for configuring a NatsKV source.

func WithLeadershipProbe added in v2.4.0

func WithLeadershipProbe(fn func() bool) NatsKVOption

WithLeadershipProbe sets a callback that the reconcile loop calls on each tick to choose between leader cadence (30s) and follower cadence (5min). When set, the fixed reconcile interval from WithReconcileInterval is ignored.

The provided function must be safe to call from any goroutine.

Parameters:

  • fn: Returns true if this instance is currently the leader

Returns:

  • NatsKVOption: Option function

func WithMetrics added in v2.5.0

func WithMetrics(m types.SourceMetrics) NatsKVOption

WithMetrics wires a types.SourceMetrics collector. The source uses the collector to expose its `parti_source_bucket_missing` gauge (set to 1 on the first unavailable-source observation — see SourceUnavailableHook for the empirical error surface — cleared on the next successful operation). The default is types.NopSourceMetrics.

Parameters:

  • m: SourceMetrics collector

Returns:

  • NatsKVOption: Option function

func WithReconcileInterval added in v2.4.0

func WithReconcileInterval(d time.Duration) NatsKVOption

WithReconcileInterval sets the periodic-reconcile ticker cadence. A value of 0 disables polling entirely. The default is 30s. When WithLeadershipProbe is also set the fixed interval is ignored in favour of the leadership-driven cadence (leader=30s / follower=5min).

Disabling the reconciler (d <= 0 with no leadership probe wired) disables the load-bearing recovery path for the source watcher: the nats.go KV watcher's Updates() channel does NOT close on a NATS server restart, and the periodic reconcile is the only mechanism that re-syncs source state after a silently stalled watcher. Disable polling only with full awareness — Start emits a one-shot WARN log line when this state is detected so the operational consequence is visible at first deploy.

Disabling the reconciler also disables bucket delete+recreate recovery: see reconcileOnce for the recovery boundary.

Parameters:

  • d: Reconcile interval (0 disables; default 30s)

Returns:

  • NatsKVOption: Option function

func WithUnavailableHook added in v2.5.0

func WithUnavailableHook(h SourceUnavailableHook) NatsKVOption

WithUnavailableHook registers a SourceUnavailableHook that fires when the partition-source bucket is observed to be unavailable. See SourceUnavailableHook for the deadlock contract and rate-limiting behavior. Without a hook, the loss is still logged and the metric is set (when WithMetrics is configured), but no escalation runs — the library cannot recreate a user-owned bucket on its own.

Parameters:

  • h: Callback invoked on bucket-loss detection

Returns:

  • NatsKVOption: Option function

func WithUpdateRetries added in v2.4.0

func WithUpdateRetries(n int) NatsKVOption

WithUpdateRetries sets the maximum number of CAS attempts for Update and Modify. A value <= 0 falls back to the default of 5. After exhausting all attempts, Update and Modify return ErrUpdateRetryExhausted.

Parameters:

  • n: Maximum number of CAS attempts (default 5)

Returns:

  • NatsKVOption: Option function

type SourceUnavailableHook added in v2.5.0

type SourceUnavailableHook func(err error)

SourceUnavailableHook fires when the partition-source bucket is unavailable on the live connection. The reconciler and watcher-restart paths each see a distinct error from the nats.go surface — jetstream.ErrBucketNotFound from a fresh jetstream.JetStream.KeyValue lookup, jetstream.ErrStreamNotFound from a cached watcher rebind, nats.ErrNoResponders from a cached jetstream.KeyValue.Get, or context.DeadlineExceeded when the source bucket cannot answer in time. The err argument hands the original sentinel to the hook so implementations can log or surface the raw cause, but **do not gate readiness on a single sentinel type**: when the hook fires, the source is unavailable.

The library does not recreate the bucket — it is caller-owned, and rebuilding it requires the operator's provisioning flow. Wire this hook into your readiness logic (e.g. flip a readiness flag and let the orchestrator rotate the pod) so the loss becomes an actionable signal rather than a silent stall.

Concurrency contract: the hook is invoked synchronously from the source's reconcile / restart goroutine. Implementations MUST be non-blocking and MUST NOT call back into the source (recursive Get / Update / Stop will deadlock against the watcher's restart path).

Rate-limiting: repeated observations within a cooldown window (default 30s, matching the default reconcile cadence) suppress the hook so the callback is not flooded. The companion [SourceMetrics] gauge stays set for the full duration of the outage and clears once a subsequent operation succeeds.

type Static

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

Static implements a partition source with a fixed list of partitions.

func NewStatic

func NewStatic(partitions []types.Partition) *Static

NewStatic creates a new static partition source.

The source returns a fixed list of partitions that never changes. Useful for testing and scenarios where partitions are known at startup.

Parameters:

  • partitions: Fixed list of partitions

Returns:

  • *Static: Initialized static source

Example:

partitions := []types.Partition{
    {Keys: []string{"tool001", "chamber1"}, Weight: 100},
    {Keys: []string{"tool001", "chamber2"}, Weight: 150},
}
src := source.NewStatic(partitions)
js, _ := jetstream.New(conn)
mgr, err := parti.NewManager(&cfg, js, src, strategy.NewConsistentHash())
if err != nil { /* handle */ }

func (*Static) List

func (s *Static) List(_ context.Context) ([]types.Partition, error)

List returns the static list of partitions.

Returns:

  • []types.Partition: The fixed list of partitions
  • error: Always nil (never fails)

func (*Static) Start

func (s *Static) Start(_ context.Context) error

Start implements PartitionSource.Start. For Static source, it validates the static partitions.

func (*Static) Stop

func (s *Static) Stop(_ context.Context) error

Stop implements PartitionSource.Stop. For Static source, this is a no-op.

func (*Static) Update

func (s *Static) Update(_ context.Context, partitions []types.Partition) error

Update updates the partition list.

This allows the static source to simulate dynamic partition changes, which is useful for testing partition refresh scenarios.

Parameters:

  • ctx: Context for the operation (unused)
  • partitions: New list of partitions

Returns:

  • error: Always nil

Example:

src := source.NewStatic(initialPartitions)
// Later: add more partitions
src.Update(context.Background(), expandedPartitions)

Jump to

Keyboard shortcuts

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