conflict

package
v1.5.1 Latest Latest
Warning

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

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

Documentation

Overview

Package conflict provides data-driven task conflict detection for Flow.

The core abstraction is Rule, a declarative struct that defines which operation pairs cannot coexist and at what scope. Rule.Conflicts() evaluates the rule against a set of active tasks.

Conflict semantics are intrinsic to what operations do to shared hardware and are therefore defined in code, not configurable by users. The built-in rule (builtinRule) captures these semantics as explicit operation pairs.

Convention: any new TaskType or ComponentType added to the codebase MUST be assessed and the appropriate pairs added to builtinRule in the same PR.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Entry

type Entry struct {
	A OperationSpec
	B OperationSpec

	// RequireComponentOverlap narrows conflict detection to task pairs
	// that share at least one component UUID. When false, any two
	// matching tasks on the same rack conflict (rack pre-filtering by
	// ListActiveTasksForRack makes an explicit rack check unnecessary).
	RequireComponentOverlap bool
}

Entry is a symmetric pair of operations that cannot coexist when the scope condition is satisfied.

type OperationSpec

type OperationSpec struct {
	OperationType string                    // e.g. "power_control", "*"
	OperationCode string                    // e.g. "power_on", "*"; "" = wildcard
	ComponentType devicetypes.ComponentType // ComponentTypeUnknown = wildcard
}

OperationSpec matches an operation by type, code, and component type. The wildcard value "*" matches any string field; ComponentTypeUnknown (0) matches any component type (including tasks with nil ComponentsByType).

func (OperationSpec) Matches

func (s OperationSpec) Matches(op OperationSpec) bool

Matches returns true if this spec matches the given operation type and code. ComponentType is NOT checked here — it is evaluated separately against the task's Attributes.ComponentsByType in Rule.Conflicts().

type Promoter

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

Promoter listens for task-completion events and promotes the next eligible waiting task for a rack to pending, then triggers its execution.

It is event-driven: the notifyCh receives rack IDs whenever a task on that rack finishes. A single background goroutine drains the channel, purges expired waiting tasks, and promotes the oldest non-expired one. A periodic ticker additionally sweeps all racks so that expiry is predictable and stranded waiting tasks are recovered after restarts.

func NewPromoter

func NewPromoter(
	store taskstore.Store,
	promoteFunc func(ctx context.Context, taskID uuid.UUID) error,
	conf PromoterConfig,
) *Promoter

NewPromoter creates a fully initialized Promoter. promoteFunc is the callback invoked to execute a task that has been promoted from waiting to pending.

func (*Promoter) Notify

func (p *Promoter) Notify(rackID uuid.UUID)

Notify queues a rack ID for promotion processing. Non-blocking: if the channel is full the notification is silently dropped (the next completion event will re-trigger processing for the rack).

func (*Promoter) Start

func (p *Promoter) Start(ctx context.Context)

Start launches the promotion event loop in a background goroutine and returns immediately. The loop runs until ctx is cancelled. If the loop exits unexpectedly (e.g. due to a recovered panic) it is restarted after RestartBackoff.

type PromoterConfig

type PromoterConfig struct {
	// SweepInterval controls how often the promoter scans all racks for
	// expired waiting tasks and stranded queues. An immediate sweep is
	// always performed on Start. Defaults to 5 minutes.
	SweepInterval time.Duration

	// NotifyChannelSize is the buffer size of the rack-notification channel.
	// Defaults to 64.
	NotifyChannelSize int

	// RestartBackoff is the delay before restarting the event loop after an
	// unexpected exit (e.g. a recovered panic). Defaults to 5 seconds.
	RestartBackoff time.Duration
}

PromoterConfig holds tunable parameters for the Promoter.

type Resolver

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

Resolver determines whether an incoming operation conflicts with active tasks on a rack using the code-defined builtinRule.

func NewResolver

func NewResolver(store taskstore.Store) *Resolver

NewResolver creates a new Resolver backed by the given store.

func (*Resolver) HasConflict

func (r *Resolver) HasConflict(
	ctx context.Context,
	incoming *taskdef.Task,
) (bool, error)

HasConflict returns true if the incoming task would conflict with any existing active task on the same rack under the builtin conflict rule.

func (*Resolver) HasScheduleConflict

func (r *Resolver) HasScheduleConflict(
	incoming operation.Wrapper,
	existing []operation.Wrapper,
) bool

HasScheduleConflict reports whether the incoming operation would conflict with any of the existing schedule operations.

This is a coarse-grained advisory check: only operation type and code are matched against the ConflictingPairs table. Component-type checks and component-UUID overlap checks are intentionally skipped because the exact components are not known at schedule creation time (they are resolved from scope rows at fire time). The check is therefore conservative — it may return true when a runtime check would not — but it will never miss a conflict that would be caught at runtime.

For precise per-component conflict detection use HasConflict, which requires full task Attributes (populated from live inventory).

type Rule

type Rule struct {
	// ConflictingPairs lists operation pairs that cannot coexist within
	// the scope. Each entry is evaluated symmetrically. Empty = all
	// active operations conflict (exclusive mode).
	ConflictingPairs []Entry

	// AtomicAcrossRacks: when true, conflict checking spans all racks in
	// the same task group — the entire multi-rack operation is rejected or
	// queued as a unit if any rack has a conflict. For V3 in the future,
	// currently always false.
	AtomicAcrossRacks bool
}

Rule declaratively defines when operations conflict. Empty ConflictingPairs means exclusive mode: any active task is a conflict.

func (*Rule) Conflicts

func (r *Rule) Conflicts(
	incoming *taskdef.Task,
	activeTasks []*taskdef.Task,
) bool

Conflicts returns true if the incoming task conflicts with any of the active tasks under this rule. A conflict requires:

  1. The operation pair matches a ConflictingPairs entry (type, code, and component type all match for the respective tasks).
  2. If RequireComponentOverlap is true, the two tasks must share at least one component UUID.

All tasks in activeTasks are assumed to be on the same rack as incoming (pre-filtered by ListActiveTasksForRack).

Special case: empty ConflictingPairs is exclusive mode — any active task is a conflict.

Jump to

Keyboard shortcuts

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