signal

package
v0.2.0 Latest Latest
Warning

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

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

Documentation

Overview

Package signal is the cross-process control-surface for `awf signal` / `awf pause` / `awf cancel`. Per Phase 3 design decision 3, it is a per-run directory of JSON files; the broker is a thin file-reader/writer struct, NOT a generic IPC layer.

Directory layout (per spec §G):

.awf/runs/<run.id>/control/
  signal-<name>-<seq>.json       # pending signal payloads
  pause.json                     # operator-requested pause (non-terminal)
  cancel.json                    # operator-requested cancel (terminal)
  consumed/
    signal-<name>-<seq>.json     # atomic-renamed here after consumption

Slice 3.5 is POSIX-only; Windows file-lock semantics need verification (Phase 3 design out-of-scope note).

Index

Constants

This section is empty.

Variables

View Source
var ErrCancelled = errors.New("signal: run cancelled (terminal)")

ErrCancelled is the sentinel engine.Run returns alongside Outcome("") when control-file polling detects cancel.json. The engine has ALREADY appended the terminal run.cancelled event; the CLI exits cleanly. `awf resume` refuses any log with a run.cancelled event.

View Source
var ErrPaused = errors.New("signal: run paused (non-terminal)")

ErrPaused is the sentinel engine.Run returns alongside Outcome("") when control-file polling detects pause.json. The CLI's runAndFinish maps this to a clean exit (rc=0) WITHOUT writing run.finished — the run is non- terminal and resumable.

Functions

func ConsumedDir

func ConsumedDir(controlDir string) string

ConsumedDir returns the consumed/ subdirectory within a control directory.

func ControlDir

func ControlDir(stateDir, runID string) string

ControlDir returns the control-directory path for the given run within a state dir. Mirrors cli/run.go's `<stateDir>/runs/<id>/log` layout pattern.

Types

type Broker

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

Broker is the per-run control-surface. One Broker per run.id, bound to its .awf/runs/<run.id>/control/ directory. The engine constructs an instance at run start; the CLI subcommands (awf signal/pause/cancel) construct an instance pointing at the same directory to write control files.

Concrete struct (no interface) — see Phase 3 design decision 8 + slice 3.5 design Q2. Substitute via constructor (`signal.NewBroker(t.TempDir(), ...)`), not via interface implementation.

Thread-safety: all methods are safe to call from concurrent goroutines. The underlying filesystem operations are the synchronization primitive (POSIX rename is atomic; O_EXCL+O_CREATE is race-free for new files).

func NewBroker

func NewBroker(controlDir string, opts ...BrokerOption) *Broker

NewBroker constructs a Broker bound to controlDir. The directory is created LAZILY — WriteSignal / WritePause / WriteCancel each MkdirAll the controlDir (and consumedDir, for atomic-rename targets) on first call. NewBroker itself does no I/O and never returns an error.

Default poll interval: 100ms (overridable via WithPollInterval).

func (*Broker) CheckPauseCancel

func (b *Broker) CheckPauseCancel() (*PauseRequest, *CancelRequest, error)

CheckPauseCancel reports whether pause.json and cancel.json exist in controlDir. Either bool may be true; both may be true (cancel-wins resolution is the caller's responsibility — engine/controls.go does that).

Returns (pauseReq, cancelReq, err). pauseReq/cancelReq are non-nil iff their respective files exist. err is non-nil only on read errors (file exists but not readable; malformed JSON is silently treated as an empty body). Reads are capped at maxControlFileBytes (L10 fix).

func (*Broker) ClearPauseCancel

func (b *Broker) ClearPauseCancel() error

ClearPauseCancel removes pause.json and cancel.json. Idempotent (missing files are not errors). Called by cli/resume.go before re-entering the engine — pause is non-terminal but stale pause.json would re-pause on the next commit; resume must clear it to make forward progress.

func (*Broker) ControlDir

func (b *Broker) ControlDir() string

ControlDir returns the configured control directory. For tests + diagnostics.

func (*Broker) Drain

func (b *Broker) Drain() []Delivery

Drain returns all pending signals (any name) without blocking. Existed in an earlier draft for use by an at-commit-boundary journal — that draft has been superseded by the background pollControls goroutine architecture (engine/controls.go).

Phase 3 minimum: Drain is NOT called by the engine — early signals are picked up at the matching await's Receive call (via the "drain first" path in Receive). Drain exists for completeness + Phase 6 obs (which may want to project pending signals); tested but not wired in slice 3.5.

func (*Broker) PollInterval

func (b *Broker) PollInterval() time.Duration

PollInterval returns the configured poll interval. Used by engine.Run's background polling goroutine (engine/controls.go) to set its ticker. Read-only after NewBroker — no setter; reconfigure via a fresh Broker.

func (*Broker) Receive

func (b *Broker) Receive(ctx context.Context, name string, timeout time.Duration) (Delivery, error)

Receive blocks until a signal of name arrives, ctx cancels, or timeout elapses (0 = no timeout). On delivery, the file is atomic-renamed into consumed/ BEFORE returning (so a crash before the engine commits signal.received leaves the file in consumed/; on resume the engine's Fold re-derives state from the log, not the broker). Returns the Delivery on success.

ctx-cancel: returns (Delivery{}, ctx.Err()). timeout: returns (Delivery{}, context.DeadlineExceeded) — caller maps to

retryable_failure per spec §4.3.

func (*Broker) ReceiveMatching

func (b *Broker) ReceiveMatching(ctx context.Context, name string, timeout time.Duration, match MatchFunc) (Delivery, error)

ReceiveMatching is Receive with a payload predicate: it blocks until a signal of name whose payload satisfies match arrives, ctx cancels, or timeout elapses (0 = no timeout). The EARLIEST-seq matching signal is atomic-renamed into consumed/ before returning; non-matching (and unpredicatable) candidates are LEFT in place for other awaits. Same crash-safety contract as Receive (a crash before the engine commits signal.received leaves the file in consumed/; Fold re-derives state from the log, not the broker).

ctx-cancel: (Delivery{}, ctx.Err()). timeout: (Delivery{}, DeadlineExceeded) — the engine maps to retryable_failure (spec §4.3), identical to "no signal".

func (*Broker) WriteCancel

func (b *Broker) WriteCancel(req CancelRequest) error

WriteCancel writes cancel.json. Idempotent.

func (*Broker) WritePause

func (b *Broker) WritePause(req PauseRequest) error

WritePause writes pause.json. Idempotent — overwrites any existing file.

func (*Broker) WriteSignal

func (b *Broker) WriteSignal(name string, payload []byte) (int, error)

WriteSignal writes signal-<name>-<seq>.json with payload bytes. seq is auto-allocated by re-reading the directory state on EACH attempt — every retry sees concurrent writers' just-committed seqs and bumps past them.

payload may be nil (signal with no payload). Empty file is written; readers distinguish via len(payload) == 0.

Returns the allocated seq on success.

Concurrency: O_EXCL is the atomicity primitive. On ErrExist, the loop re-runs nextSeq() to pick up the up-to-date max, then retries — convergence is O(N) per writer for N concurrent invocations. maxAttempts=50 covers reasonable bursts (CLI operators rarely fire >50 signals concurrently); exhaustion returns an error (slice 3.5 design Q6 + critique C4).

type BrokerOption

type BrokerOption func(*Broker)

BrokerOption configures a Broker. Currently only one option exists; future additions (fsnotify, custom file modes) extend additively.

func WithPollInterval

func WithPollInterval(d time.Duration) BrokerOption

WithPollInterval sets how often Broker.Receive polls the control directory for new signal files. Production default is 100ms; tests use ~1ms for determinism. Out-of-range values (<=0) are silently ignored; production default applies.

type CancelRequest

type CancelRequest struct {
	Reason string `json:"reason,omitempty"`
}

CancelRequest is the parsed body of cancel.json.

type Delivery

type Delivery struct {
	Name    string
	Seq     int
	Payload []byte
}

Delivery is the result of consuming one signal file. Name + Seq identify the source; Payload is the file's raw bytes (empty for no-payload signals).

type MatchFunc

type MatchFunc func(payload []byte) (bool, error)

MatchFunc decides whether a buffered signal's payload satisfies a keyed-await `where:` clause. Returns (true, nil) to consume this candidate, (false, nil) to skip it (leave it buffered for another await), or (false, err) when the payload cannot be predicated (e.g. not JSON) — treated as skip-this-candidate by tryConsumeMatching (the engine builds the predicate; see engine/signal_step.go).

type PauseRequest

type PauseRequest struct {
	NodePath string `json:"node_path,omitempty"`
	Reason   string `json:"reason,omitempty"`
}

PauseRequest is the parsed body of pause.json.

Jump to

Keyboard shortcuts

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