Help us shape the future of Go! Take the 2026 Go Developer Survey

worker

package
v1.21.20 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: 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