common

package
v0.0.0-...-e3c07e6 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package common holds types shared across the router, queue, stream, outbox, and platform packages: messages, dispatch modes, mediation results, outbox status, configuration shapes, and warnings.

JSON tags match the wire contract exactly (camelCase, omitempty for optionals, SCREAMING_SNAKE_CASE for enums) so wire format is byte-compatible.

Index

Constants

View Source
const (
	DeliveryOutcomeHeader = "FlowCatalyst-Outcome"
	DeliveryOutcomeReject = "reject"
)

A delivery target's terminal "do not retry" answer: HTTP 422 carrying DeliveryOutcomeHeader: DeliveryOutcomeReject. Dispatch-job and scheduled-job delivery fail the attempt's job at once instead of spending the remaining retries (function runner, docs/function-runner-plan.md §13.2).

View Source
const DefaultDispatchMode = DispatchNextOnError

DefaultDispatchMode is what an unspecified mode means, everywhere: keep a message group in sequence, one at a time, and move on to the next message if one fails.

It was IMMEDIATE, which is the only mode with no ordering at all — so any producer that omitted the field silently gave up sequencing, and the loss showed only under load, where concurrent dispatch actually gets to interleave. A default that quietly weakens a guarantee is the wrong way round: ordering is cheap to opt out of (set IMMEDIATE) and expensive to discover you never had.

Variables

AllOutboxItemTypes is the iterable set for type-aware polling loops.

Functions

func IsDeliveryRejected

func IsDeliveryRejected(status int, outcomeHeader string) bool

IsDeliveryRejected reports whether a response is the terminal reject.

Types

type DispatchMode

type DispatchMode string

DispatchMode controls ordering behavior within a message group. Shared across platform, scheduler, and router.

const (
	DispatchImmediate    DispatchMode = "IMMEDIATE"
	DispatchNextOnError  DispatchMode = "NEXT_ON_ERROR"
	DispatchBlockOnError DispatchMode = "BLOCK_ON_ERROR"
)

func ParseDispatchMode

func ParseDispatchMode(s string) DispatchMode

ParseDispatchMode maps a wire/stored value to a mode. Empty means unspecified, which is DefaultDispatchMode. Anything unrecognised is a producer bug: it also takes the default, but says so, because silently treating a typo as "no ordering" is how the previous default hid.

Deliberately still lenient — this is the one X-06 exemption, ruled X-01: unknown/absent dispatch mode defaults to NEXT_ON_ERROR rather than rejecting. Do not convert this to the (T, bool) strict shape used by ParseDispatchStatus and the rest of this package's enum parsers.

func (DispatchMode) RequiresOrdering

func (d DispatchMode) RequiresOrdering() bool

RequiresOrdering reports whether the mode demands FIFO processing.

type DispatchStatus

type DispatchStatus string

DispatchStatus is the lifecycle state of a dispatch job. Wire format is SCREAMING_SNAKE_CASE; matches the TypeScript SDK.

const (
	DispatchPending    DispatchStatus = "PENDING"
	DispatchQueued     DispatchStatus = "QUEUED"
	DispatchProcessing DispatchStatus = "PROCESSING"
	DispatchCompleted  DispatchStatus = "COMPLETED"
	DispatchFailed     DispatchStatus = "FAILED"
	DispatchCancelled  DispatchStatus = "CANCELLED"
	DispatchExpired    DispatchStatus = "EXPIRED"
)

func ParseDispatchStatus

func ParseDispatchStatus(s string) (DispatchStatus, bool)

ParseDispatchStatus parses a stored/wire status value. IN_PROGRESS and ERROR are accepted legacy aliases of PROCESSING and FAILED — not just wire input, but real values `msg_dispatch_jobs.status` has held (see dispatchjob.GroupHoldersSQL, which still matches 'ERROR' by design so old rows keep blocking as they always did). Returns ok=false for anything else — callers MUST reject on ok=false rather than coerce an unrecognised value to PENDING (X-06: a loud read error, never a silent default — a corrupted terminal status silently reappearing as PENDING could resurrect a job that already completed or failed). Follows the (T, bool) shape of ParseOutboxItemType.

func (DispatchStatus) IsSuccessful

func (s DispatchStatus) IsSuccessful() bool

IsSuccessful reports whether the status is the success terminal.

func (DispatchStatus) IsTerminal

func (s DispatchStatus) IsTerminal() bool

IsTerminal reports whether a status will not change further.

type EnhancedPoolMetrics

type EnhancedPoolMetrics struct {
	TotalSuccess     uint64 `json:"totalSuccess"`
	TotalFailure     uint64 `json:"totalFailure"`
	TotalRateLimited uint64 `json:"totalRateLimited"`
	// TotalSuppressed counts messages ACKed without delivery because their
	// group was suppressed by the GroupFlushRegistry (R-53). Kept separate
	// from TotalSuccess/TotalFailure: no delivery was attempted, so neither
	// bucket fits, and a pool suppressing a flushed group should read
	// busy-but-suppressed rather than idle.
	TotalSuppressed uint64                `json:"totalSuppressed"`
	SuccessRate     float64               `json:"successRate"`
	ProcessingTime  ProcessingTimeMetrics `json:"processingTime"`
	Last5Min        WindowedMetrics       `json:"last5Min"`
	Last30Min       WindowedMetrics       `json:"last30Min"`
}

EnhancedPoolMetrics is the rolling-window snapshot embedded in PoolStats.Metrics.

type InFlightMessage

type InFlightMessage struct {
	MessageID       string
	BrokerMessageID string
	PoolCode        string
	QueueIdentifier string
	StartedAt       time.Time
	// LastSeenAt is refreshed every time the broker redelivers this message
	// (receipt-handle swap). The reaper ages entries on LastSeenAt, not
	// StartedAt: while the broker still holds the message it keeps
	// redelivering (refreshing this), so a long-buffered entry is never
	// reaped out from under the dedup; once the broker no longer has the
	// message, refreshes stop and the entry ages out.
	LastSeenAt     time.Time
	MessageGroupID string
	BatchID        string
	ReceiptHandle  string
	// DeferredUntil is set while the router has handed this copy back to the
	// broker for capacity (Pool.deferMsg) and expects it to return at about
	// this time. The entry is KEPT, not removed, so that a second copy of the
	// same message id published meanwhile — the platform's stale-QUEUED
	// recovery republishes a job whose message is parked longer than its
	// stale threshold, with a fresh broker id — is recognised as a duplicate
	// and deleted instead of being deferred alongside it (owner, 2026-09-22:
	// "500 in flight but only 200 pending — use the messageId, not the SQS
	// id"). Zero while the copy is in the pipeline.
	DeferredUntil time.Time
	// Attempts is >0 once the message has failed at least once and is being
	// retried in-pipeline. The stall detector never force-NACKs such an entry
	// (a live retry owns it, and yanking it away would double-deliver), and
	// the reaper exempts it from the idle bound — but only for as long as
	// LastRetryAt says that retry is still live.
	Attempts uint
	// LastRetryAt is when this entry last recorded a new in-pipeline attempt
	// (InFlightTracker.MarkRetrying, the single mutator of Attempts). It is
	// what makes the reaper's Attempts>0 exemption FINITE.
	//
	// The exemption used to be unconditional, on the reasoning that the retry
	// budget (maxInPipelineAttempts) always ends in a release that removes the
	// entry. A drainer cancelled mid-retry never reaches that release — it
	// re-fronts the message, clears the group's working flag and returns — so
	// the entry outlived its owner and, being permanently exempt, could never
	// be reaped. Every subsequent redelivery of the message was then dropped
	// as a duplicate of a copy nobody owned, for ever.
	//
	// Zero while Attempts is 0.
	LastRetryAt time.Time
}

InFlightMessage tracks a message currently being processed.

func NewInFlightMessage

func NewInFlightMessage(m *Message, brokerID, queueID, batchID, receipt string) *InFlightMessage

NewInFlightMessage constructs a tracker.

func (*InFlightMessage) ElapsedSeconds

func (m *InFlightMessage) ElapsedSeconds() int64

ElapsedSeconds returns how long the message has been in flight.

func (*InFlightMessage) UpdateReceiptHandle

func (m *InFlightMessage) UpdateReceiptHandle(h string)

UpdateReceiptHandle replaces the receipt handle on broker redelivery and refreshes LastSeenAt (the broker evidently still holds the message).

type LeaderElectionConfig

type LeaderElectionConfig struct {
	Enabled                  bool
	RedisURL                 string
	LockKey                  string
	LockTTLSeconds           uint64
	HeartbeatIntervalSeconds uint64
	InstanceID               string
}

LeaderElectionConfig is the unified leader-election configuration shared by the outbox and standby subsystems.

func NewLeaderElectionConfig

func NewLeaderElectionConfig(redisURL string) LeaderElectionConfig

NewLeaderElectionConfig creates a config with sane defaults.

type MediationOutcome

type MediationOutcome struct {
	Result       MediationResult
	DelaySeconds int // 0 if no delay
	StatusCode   int // 0 if not from HTTP
	ErrorMessage string

	// FlushGroup is set when a 2xx body carried {"flushGroup": true}: the
	// target is asking us to stop delivering this message's group and just
	// ACK its siblings. DelaySeconds carries how long to suppress for.
	// Honoured only alongside a successful delivery — a target that wants
	// the message back cannot also discard the group.
	FlushGroup bool

	// PreFlight marks an outcome decided BEFORE any request went out — an
	// unsupported mediation type, an unusable target URL, a payload that would
	// not marshal. Such an outcome is no evidence about the target's health in
	// either direction, so the circuit breaker must ignore it. Recording one as
	// a success actively masks a failing endpoint: a misconfigured URL would
	// hold the breaker closed on a host that is down.
	//
	// Set only by PreFlightError, so it cannot be forgotten at a call site.
	PreFlight bool
}

MediationOutcome carries the result plus optional retry-after delay.

func CircuitOpen

func CircuitOpen(delaySec int) MediationOutcome

CircuitOpen builds a circuit-open outcome: the per-endpoint breaker is open, so no HTTP call was attempted. The pool DEFERS with delaySec (the breaker's reset timeout) and, for ordered messages, marks the batch+group failed to preserve FIFO — 1:1 with the prior pool circuit-open path.

func Deferred

func Deferred(delaySec int, msg string) MediationOutcome

Deferred builds an ack=false outcome: the target answered 2xx but told us to retry later ({"ack": false, "delaySeconds": N}).

func ErrorConfig

func ErrorConfig(status int, msg string) MediationOutcome

ErrorConfig builds a 4xx outcome.

func ErrorConnection

func ErrorConnection(msg string) MediationOutcome

ErrorConnection builds a connection-error outcome. Default delay 30s.

func ErrorProcess

func ErrorProcess(delaySec int, msg string) MediationOutcome

ErrorProcess builds a 502/503/504-or-timeout ("target unavailable") outcome with optional retry delay. Every other 5xx is ErrorConfig instead — see MediationErrorProcess.

func PreFlightError

func PreFlightError(msg string) MediationOutcome

PreFlightError builds a permanent rejection for a message that never reached the network. It is an ErrorConfig — the message is unusable as it stands and retrying it unchanged cannot help — flagged so the breaker stays out of it.

func RateLimited

func RateLimited(retryAfterSec int) MediationOutcome

RateLimited builds a 429 outcome with the supplied Retry-After delay.

func Success

func Success(status int) MediationOutcome

Success builds a successful outcome carrying the status the target actually returned.

The status is a parameter rather than a hard-coded 200 because it used to be hard-coded, and every success — 201, 202, 204 — was recorded as 200. The error and ack=false paths beside it copied the real status all along, so the logs were confidently wrong about exactly one class of response. Taking it as an argument means the compiler asks the question at each new call site; the flushGroup branch was added later and silently inherited the wrong answer.

type MediationResult

type MediationResult int

MediationResult classifies the outcome of an attempt to deliver a message to its target endpoint.

const (
	// MediationSuccess is a 2xx delivery — ACK.
	MediationSuccess MediationResult = iota
	// MediationErrorConfig is a 4xx — ACK to prevent infinite retries.
	MediationErrorConfig
	// MediationErrorProcess is a 502/503/504 ("target unavailable") or a
	// transport-level timeout — NACK for retry. Every other 5xx means the
	// app was reached and answered, and classifies as MediationErrorConfig
	// instead (permanent, like a 4xx).
	MediationErrorProcess
	// MediationErrorConnection is a connection failure — NACK for retry.
	MediationErrorConnection
	// MediationRateLimited is HTTP 429. NACK with Retry-After delay, but
	// don't count toward circuit-breaker failures: destination is
	// healthy, just throttling us.
	MediationRateLimited
	// MediationCircuitOpen means the per-endpoint breaker is open; no HTTP
	// call was attempted. DEFER (not a failure) until the breaker may probe.
	MediationCircuitOpen
	// MediationDeferred is a 2xx whose body carried {"ack": false}: the
	// target is healthy but asked us to come back later (e.g. the record is
	// blocked behind an earlier failure). Requeued on the pool's deferred
	// backoff curve, flooring at any requested delaySeconds — no in-pipeline
	// retry, no circuit-breaker impact.
	MediationDeferred
)

type MediationType

type MediationType string

MediationType is the kind of mediation (currently only HTTP).

const (
	MediationTypeHTTP MediationType = "HTTP"
)

type Message

type Message struct {
	ID              string        `json:"id"`
	PoolCode        string        `json:"poolCode,omitempty"`
	AuthToken       *string       `json:"authToken,omitempty"`
	SigningSecret   *string       `json:"signingSecret,omitempty"`
	MediationType   MediationType `json:"mediationType"`
	MediationTarget string        `json:"mediationTarget"`
	MessageGroupID  *string       `json:"messageGroupId,omitempty"`
	HighPriority    bool          `json:"highPriority,omitempty"`
	DispatchMode    DispatchMode  `json:"dispatchMode,omitempty"`
}

Message is the core message structure that flows through the system. Compatible with the historical MessagePointer wire shape.

func (*Message) GroupID

func (m *Message) GroupID() string

GroupID returns the message group, or "" when the message is ungrouped.

type OutboxItemType

type OutboxItemType string

OutboxItemType is the kind of payload an outbox row carries.

const (
	OutboxItemEvent       OutboxItemType = "EVENT"
	OutboxItemDispatchJob OutboxItemType = "DISPATCH_JOB"
	OutboxItemAuditLog    OutboxItemType = "AUDIT_LOG"
)

func ParseOutboxItemType

func ParseOutboxItemType(s string) (OutboxItemType, bool)

ParseOutboxItemType accepts case-insensitive plus hyphenated forms. Returns ok=false on unknown input.

func (OutboxItemType) APIPath

func (t OutboxItemType) APIPath() string

APIPath returns the platform endpoint that consumes this item type.

type OutboxStatus

type OutboxStatus int

OutboxStatus stores the integer-coded status used by the historical outbox schema. Wire string is SCREAMING_SNAKE_CASE.

const (
	OutboxPending       OutboxStatus = 0
	OutboxSuccess       OutboxStatus = 1
	OutboxBadRequest    OutboxStatus = 2
	OutboxInternalError OutboxStatus = 3
	OutboxUnauthorized  OutboxStatus = 4
	OutboxForbidden     OutboxStatus = 5
	OutboxGatewayError  OutboxStatus = 6
	OutboxInProgress    OutboxStatus = 9
)

func FromOutboxCode

func FromOutboxCode(c int) OutboxStatus

FromOutboxCode maps an integer to OutboxStatus; unknown codes default to PENDING.

func (OutboxStatus) Code

func (s OutboxStatus) Code() int

Code returns the integer code (for DB storage).

func (OutboxStatus) IsRetryable

func (s OutboxStatus) IsRetryable() bool

IsRetryable reports whether this status will be retried by the outbox processor.

func (OutboxStatus) IsTerminal

func (s OutboxStatus) IsTerminal() bool

IsTerminal reports whether this status will not be retried.

func (OutboxStatus) String

func (s OutboxStatus) String() string

String returns the SCREAMING_SNAKE_CASE wire representation.

type PoolConfig

type PoolConfig struct {
	Code               string  `json:"code"`
	Concurrency        uint32  `json:"concurrency"`
	RateLimitPerMinute *uint32 `json:"rateLimitPerMinute,omitempty"`
}

PoolConfig is the per-pool routing configuration.

type ProcessingTimeMetrics

type ProcessingTimeMetrics struct {
	AvgMs       float64 `json:"avgMs"`
	MinMs       uint64  `json:"minMs"`
	MaxMs       uint64  `json:"maxMs"`
	P50Ms       uint64  `json:"p50Ms"`
	P95Ms       uint64  `json:"p95Ms"`
	P99Ms       uint64  `json:"p99Ms"`
	SampleCount uint64  `json:"sampleCount"`
}

ProcessingTimeMetrics summarises latency in milliseconds with the percentiles the dashboard renders.

type QueueConfig

type QueueConfig struct {
	Name              string `json:"queueName"`
	URI               string `json:"queueUri"`
	Connections       uint32 `json:"connections"`
	VisibilityTimeout uint32 `json:"visibilityTimeout"`
}

QueueConfig is the per-queue connection configuration.

The wire contract is the camelCase shape emitted by the central config service: {queueName, queueUri, connections, visibilityTimeout}. This MUST stay interoperable — a Go router dropped into an existing deployment polls the same config service, and a Go platform serving config must be readable by existing routers. UnmarshalJSON below additionally accepts the legacy {name, uri} keys so configs produced by older Go builds still load.

func (*QueueConfig) UnmarshalJSON

func (q *QueueConfig) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts both the canonical camelCase keys (queueName, queueUri) and the legacy aliases (name, uri), and applies the standard defaults: name falls back to uri, connections defaults to 1, visibilityTimeout defaults to 120.

type QueuedMessage

type QueuedMessage struct {
	Message         Message
	ReceiptHandle   string
	BrokerMessageID string // empty if not provided
	QueueIdentifier string
	// BatchID is a router-assigned grouping over messages received in the
	// same poll batch. It is set by the pool's
	// poll loop, not the broker, and is informational only.
	BatchID string
	// Attempts counts how many in-pipeline mediation attempts this delivery
	// has already had (0 on first dispatch). The pool increments it on each
	// retry so it can recognise a re-dispatch (skip re-tracking) and grow the
	// backoff. Internal-only; never crosses the wire.
	Attempts uint
}

QueuedMessage is a Message received from a queue with broker tracking.

type RouterConfig

type RouterConfig struct {
	ProcessingPools []PoolConfig  `json:"processingPools"`
	Queues          []QueueConfig `json:"queues"`
}

RouterConfig is what the router fetches from its config source.

type StallConfig

type StallConfig struct {
	Enabled               bool   `json:"enabled"`
	StallThresholdSeconds uint64 `json:"stallThresholdSeconds"`
	ForceNackStalled      bool   `json:"forceNackStalled"`
	ForceNackAfterSeconds uint64 `json:"forceNackAfterSeconds"`
	NackDelaySeconds      uint32 `json:"nackDelaySeconds"`
}

StallConfig controls stall detection in the router.

func DefaultStallConfig

func DefaultStallConfig() StallConfig

DefaultStallConfig returns the standard defaults.

type WindowedMetrics

type WindowedMetrics struct {
	SuccessCount       uint64                `json:"successCount"`
	FailureCount       uint64                `json:"failureCount"`
	RateLimitedCount   uint64                `json:"rateLimitedCount"`
	SuccessRate        float64               `json:"successRate"`
	ThroughputPerSec   float64               `json:"throughputPerSec"`
	ProcessingTime     ProcessingTimeMetrics `json:"processingTime"`
	WindowStart        time.Time             `json:"windowStart"`
	WindowDurationSecs uint64                `json:"windowDurationSecs"`
}

WindowedMetrics is the per-window slice of pool throughput.

Jump to

Keyboard shortcuts

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