worker

package
v1.20.1 Latest Latest
Warning

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

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

Documentation

Overview

Package worker is the shared runtime for TMI Component Platform worker binaries (tmi-extractor, tmi-chunk-embed). It owns the NATS JetStream connection bootstrap, the durable-consumer loop with at-least-once and idempotency handling, and the heartbeat publisher. It is framework-free: no Gin, no GORM, no internal/config — a worker binary that imports this package and pkg/extract / pkg/jobenvelope pulls in nothing else from the monolith.

Index

Constants

View Source
const (
	// OutcomeAck marks the message processed successfully.
	OutcomeAck outcome = iota
	// OutcomeNak requests redelivery (transient failure).
	OutcomeNak
	// OutcomeTerm permanently drops the message (terminal failure).
	OutcomeTerm
)
View Source
const (
	// SubjectExtractPrefix is the NATS subject prefix for extraction job messages
	// (e.g., "jobs.extract.<jobID>").
	SubjectExtractPrefix = "jobs.extract."

	// SubjectChunkEmbedPrefix is the NATS subject prefix for chunk-and-embed job
	// messages (e.g., "jobs.chunkembed.<jobID>").
	SubjectChunkEmbedPrefix = "jobs.chunkembed."

	// SubjectResultPrefix is the NATS subject prefix for job result messages
	// (e.g., "jobs.result.<jobID>").
	SubjectResultPrefix = "jobs.result."

	// SubjectDLQ is the NATS subject for dead-letter-queue messages: jobs that
	// have exhausted all delivery retries.
	SubjectDLQ = "jobs.dlq"

	// SubjectMaxDeliverAdvisory is the wildcard subject on which JetStream
	// publishes a MAX_DELIVERIES advisory when a message exhausts a consumer's
	// MaxDeliver. The concrete subject is
	// "$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.<stream>.<consumer>".
	SubjectMaxDeliverAdvisory = "$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>"

	// SubjectHeartbeatPrefix is the NATS subject prefix for component heartbeat
	// messages (e.g., "components.heartbeat.<componentName>").
	SubjectHeartbeatPrefix = "components.heartbeat."
)
View Source
const DLQAdvisoryStream = "TMI_DLQ_ADVISORY"

DLQAdvisoryStream is the JetStream stream that durably captures MAX_DELIVERIES consumer advisories so the monolith's DLQ producer survives restarts (a plain core-NATS subscription would miss advisories fired while the monolith is down).

View Source
const DLQStream = "TMI_DLQ"

DLQStream is the dead-letter JetStream stream bound to SubjectDLQ ("jobs.dlq"). The monolith creates it, publishes dead-lettered Job envelopes to it, and consumes from it (see api/dlq_producer.go and the ResultConsumer DLQ subscription). It is not owned by any per-component stream.

View Source
const JobsStreamPrefix = "TMI_"

JobsStreamPrefix is the prefix the controller's streamNameFor prepends to the sanitized component name to produce the per-component JetStream stream name. Plan 1's render_jetstream.go is the source of truth for this value.

View Source
const PayloadBucket = "TMI_PAYLOADS"

PayloadBucket is the JetStream Object Store bucket name used for payload-by-reference: large job payloads are stored here and the job envelope carries only the object key. Plan 2 creates this bucket at worker startup; the Plan 1 controller does not render it.

View Source
const ResultStream = "TMI_RESULTS"

ResultStream is the dedicated JetStream stream that Plan 2 workers create for jobs.result.* subjects. It is not owned by any Plan 1 per-component stream; the monolith result-consumer (Plan 3) and the workers both bind it.

Variables

This section is empty.

Functions

func ChunkEmbedSubject

func ChunkEmbedSubject(jobID string) string

ChunkEmbedSubject returns the NATS subject for a chunk-and-embed job with the given job ID. SEM@6df2ea658f2595a125e17591a26dbca6df6f71ad: build the NATS subject for dispatching a chunk-embed job by job ID (pure)

func ConsumerNameFor

func ConsumerNameFor(componentName string) string

ConsumerNameFor returns the durable JetStream consumer name for a component, mirroring the controller's consumerNameFor function in render_jetstream.go. Example: "tmi-chunk-embed" → "TMI_CHUNK_EMBED_CONSUMER". SEM@6df2ea658f2595a125e17591a26dbca6df6f71ad: build the durable JetStream consumer name for a worker component, matching the KEDA ScaledObject (pure)

func EnvDuration

func EnvDuration(key string, fallback time.Duration) time.Duration

EnvDuration parses key as a Go duration, returning fallback if it is unset/empty or fails to parse. SEM@fb53ed13960634e8e70bfe57a75d2305f28c4174: parse an environment variable as a duration, returning a fallback on missing or invalid input (pure)

func EnvOr

func EnvOr(key, fallback string) string

EnvOr returns the value of key, or fallback if it is unset/empty. SEM@fb53ed13960634e8e70bfe57a75d2305f28c4174: fetch an environment variable with a fallback default (pure)

func HeartbeatSubject

func HeartbeatSubject(component string) string

HeartbeatSubject returns the NATS subject for a component's heartbeat messages. The component argument is passed through UNSANITIZED: NATS subjects use "." as a hierarchy delimiter (not an illegal character), so sanitizing would corrupt the subject hierarchy. SEM@6df2ea658f2595a125e17591a26dbca6df6f71ad: build the NATS subject for a worker component heartbeat by component name (pure)

func MustEnv

func MustEnv(key string) (string, error)

MustEnv returns the value of key or an error if it is unset/empty. SEM@fb53ed13960634e8e70bfe57a75d2305f28c4174: fetch a required environment variable or return an error if absent (pure)

func PayloadRefForJob

func PayloadRefForJob(ref, jobID string) bool

PayloadRefForJob reports whether an object_ref names a blob that belongs to the given job. Blob names embed the job ID in one of the shipped patterns: the publisher's input blob ("job-<id>-source") or a stage-output blob whose leading path segment is the job ID ("<id>/extracted", "<id>/result"). Envelope-supplied refs are worker-controlled, so consumers MUST check this before acting on a ref — honoring an arbitrary ref would let a forged envelope delete another job's blobs. SEM@d056a3ea026249d40d05ab6af7f092a043f72c7a: validate that an object ref belongs to the given job ID, rejecting forged refs (pure)

func ResultSubject

func ResultSubject(jobID string) string

ResultSubject returns the NATS subject for the result of a specific job. SEM@6df2ea658f2595a125e17591a26dbca6df6f71ad: build the NATS subject for publishing a job result by job ID (pure)

func RunConsumer

func RunConsumer(ctx context.Context, conn *Conn, cfg ConsumerConfig, handle JobHandler) error

RunConsumer creates the durable consumer and dispatches messages to the handler until ctx is cancelled. It blocks; run it on the main goroutine. SEM@e69b1723153a31aa74eb58c885a3ca54a9cbb016: bind a durable JetStream consumer and dispatch jobs to the handler until context is cancelled

func RunHeartbeat

func RunHeartbeat(ctx context.Context, conn *Conn, instanceID string, interval time.Duration)

RunHeartbeat publishes a Heartbeat on the component's heartbeat subject every interval until ctx is cancelled. It is meant to run in its own goroutine; a publish failure is logged and retried on the next tick. SEM@71c1d8554ecca870da2bafa898b79d1c29d43ebf: publish periodic heartbeat messages to NATS until context is cancelled (mutates shared state)

func SanitizeName

func SanitizeName(s string) string

SanitizeName upcases s and replaces JetStream-illegal characters (".", "-", " ") with "_". It mirrors the unexported sanitizeName function in internal/platform/controller/render_jetstream.go; the two implementations MUST stay in sync so that a worker can derive the same stream and consumer names as the controller. SEM@6df2ea658f2595a125e17591a26dbca6df6f71ad: convert a component name to an uppercase, underscore-safe NATS identifier (pure)

func StreamNameFor

func StreamNameFor(componentName string) string

StreamNameFor returns the JetStream stream name for a component, mirroring the controller's streamNameFor function in render_jetstream.go. Example: "tmi-extractor" → "TMI_TMI_EXTRACTOR". SEM@6df2ea658f2595a125e17591a26dbca6df6f71ad: build the JetStream stream name for a worker component (pure)

Types

type Config

type Config struct {
	// NATSURL is the NATS server URL (env TMI_NATS_URL).
	NATSURL string
	// ComponentName is this worker's TMIComponent name (env TMI_COMPONENT_NAME).
	ComponentName string
}

Config is the worker's NATS bootstrap configuration, read from env vars. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: NATS bootstrap configuration holding server URL and component name (pure)

func ConfigFromEnv

func ConfigFromEnv() (Config, error)

ConfigFromEnv builds a Config from the standard worker env vars. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: build NATS worker config from required environment variables (pure)

type Conn

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

Conn bundles a NATS connection, a JetStream context, and the payload Object Store handle. It is the worker's single handle to the bus. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: worker handle bundling a NATS connection, JetStream context, and payload object store (pure)

func Connect

func Connect(ctx context.Context, cfg Config) (*Conn, error)

Connect dials NATS, opens a JetStream context, and ensures the payload Object Store bucket exists.

If TMI_NATS_CREDS is set in the environment, it is used as the path to a NATS credentials file, giving each component its own bus identity once the server enables authorization. Unset preserves the credential-less default. If TMI_NATS_TLS_CA_FILE is set (see internal/tlsconfig), the connection uses TLS with that CA and, when TMI_NATS_TLS_CERT_FILE/TMI_NATS_TLS_KEY_FILE are set, a client certificate. SEM@946ec29: connect to NATS (TLS when configured), open JetStream, and ensure the payload object store

func (*Conn) Close

func (c *Conn) Close()

Close closes the NATS connection. In-flight messages are not drained. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: close the NATS connection without draining in-flight messages

func (*Conn) Config

func (c *Conn) Config() Config

Config returns the connection's config. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: return the connection's NATS bootstrap config (pure)

func (*Conn) DeletePayload

func (c *Conn) DeletePayload(ctx context.Context, ref string) error

DeletePayload removes a blob by the object_ref produced by PutPayload. It is idempotent from the caller's perspective: deleting an absent blob is treated as success so result-consumer cleanup never blocks on a double-delivery. SEM@b4d93cd08868f7468a93b099aca5217f7b263d56: delete a payload blob by object ref; idempotent if already absent

func (*Conn) GetPayload

func (c *Conn) GetPayload(ctx context.Context, ref string) ([]byte, error)

GetPayload reads a blob by the object_ref produced by PutPayload. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: fetch a blob from the payload object store by object ref

func (*Conn) JetStream

func (c *Conn) JetStream() jetstream.JetStream

JetStream returns the JetStream context for consumer/publish wiring. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: return the JetStream context for consumer and publish wiring (pure)

func (*Conn) Publish

func (c *Conn) Publish(ctx context.Context, subject string, data []byte) error

Publish publishes a pre-marshaled message to a JetStream subject (durable, stream-backed). For ephemeral signals such as heartbeats use PublishCore. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: publish a message to a durable JetStream subject

func (*Conn) PublishCore

func (c *Conn) PublishCore(subject string, data []byte) error

PublishCore publishes a message over core NATS (fire-and-forget, no JetStream stream or persistence). Use this for ephemeral signals such as heartbeats. Job messages that must be durable go through Publish. SEM@71c1d8554ecca870da2bafa898b79d1c29d43ebf: publish a fire-and-forget message over core NATS without persistence

func (*Conn) PublishFailureResult

func (c *Conn) PublishFailureResult(ctx context.Context, jobID, reasonCode, detail string) error

PublishFailureResult publishes a terminal "failed" Result envelope for the given job and returns a terminal *JobError so the consumer terminates the message. Handlers perform their own error classification to derive reasonCode/detail, then call this shared helper for the common build-Result -> marshal -> publish -> return-terminal-JobError core. A marshal or publish failure is returned as-is (the latter is transient and triggers redelivery). SEM@fcd7743e746718c31b33ef56fb3ba2f8ccf669c7: publish a terminal job failure result and return a terminal job error (mutates shared state)

func (*Conn) PutPayload

func (c *Conn) PutPayload(ctx context.Context, name string, data []byte) (string, error)

PutPayload writes bytes to the Object Store under the given name and returns the object_ref to carry in an envelope. SEM@9d9b68ad12f48fd7a991a43f29615cea731c76ca: store a blob in the payload object store and return its object ref

type ConsumerConfig

type ConsumerConfig struct {
	// StreamName is the JetStream stream the consumer binds. The controller
	// renders one stream per component (TMI_<NAME>); RunConsumer creates the
	// stream if it does not yet exist.
	StreamName string
	// Durable is the durable consumer name (stable across restarts).
	Durable string
	// FilterSubject is the subject filter (e.g. "jobs.extract.>").
	FilterSubject string
	// AckWait is the redelivery timeout — also the JetStream-side backstop
	// for a worker that dies mid-job.
	AckWait time.Duration
	// MaxDeliver caps redeliveries before JetStream dead-letters.
	MaxDeliver int
}

ConsumerConfig configures the durable consumer. SEM@3b4afc57df700de14d06ec4e93a7038dcf52b9d2: parameters for binding a durable JetStream consumer to a stream and subject (pure)

type Heartbeat

type Heartbeat struct {
	// Component is the TMIComponent name.
	Component string `json:"component"`
	// InstanceID identifies the publishing pod/process.
	InstanceID string `json:"instance_id"`
	// SentAt is the publish timestamp.
	SentAt time.Time `json:"sent_at"`
}

Heartbeat is the liveness message a worker publishes on components.heartbeat.<component>. The monolith uses it to distinguish "type declared, no healthy instance" from "instances present". SEM@0e54997b95671dd5ea0319130f237e438d611479: heartbeat payload carrying component name, instance ID, and publish timestamp (pure)

type JobError

type JobError struct {
	// ReasonCode is a pkg/extract Reason* constant (or a status string).
	ReasonCode string
	// Detail is optional human-readable context.
	Detail string
	// Terminal is true when redelivery cannot help.
	Terminal bool
}

JobError is a typed, terminal failure a handler returns when a job can never succeed (malformed input, unsupported format, timeout). The consumer terminates such a message rather than redelivering it. SEM@3b4afc57df700de14d06ec4e93a7038dcf52b9d2: typed terminal job failure signalling whether redelivery can help (pure)

func (*JobError) Error

func (e *JobError) Error() string

SEM@3b4afc57df700de14d06ec4e93a7038dcf52b9d2: format a JobError as a human-readable string (pure)

type JobHandler

type JobHandler func(ctx context.Context, job jobenvelope.Job) error

JobHandler processes one decoded job. Returning nil acks the message; returning a terminal *JobError terminates it; any other error naks it for redelivery. The handler is responsible for publishing the result envelope for terminal failures BEFORE returning the *JobError — the consumer only decides ack/nak/term, it does not publish results. SEM@3b4afc57df700de14d06ec4e93a7038dcf52b9d2: callback contract for processing a decoded job and signalling ack/nak/term via error (pure)

Jump to

Keyboard shortcuts

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