ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Worker.Job

Description

Ack within the visibility budget during job processing.

A received message is hidden only for the queue's visibility window. The worker acks on success; before a publish that may run long it calls extendVisibility to hold the message before the window lapses; on a transient failure it does not ack, so the message redelivers. A batch is processed sequentially, so each job has the full visibility budget rather than competing with its batch-mates for it.

Synopsis

Documentation

data JobOutcome Source #

The terminal outcome of processing one mirror job, deciding whether the message is acked or left to redeliver.

Constructors

Succeeded

The publish succeeded, so the job is acked. This covers an idempotent redelivery too: a version already present at the mirror target answers a status the ecosystem's codec classifies as success (npm's 409), so it surfaces here as Succeeded rather than a distinct case -- as does the same presence confirmed by the pre-fetch probe, before any bytes moved.

Dropped Text

A non-retryable rejection: the bytes did not match the re-admitted artifact's digest (tamper), or the publish URL was unformable (misconfiguration). Redelivery cannot help, so the job is acked to retire it after alarming. Carries the reason.

DeadLettered Text

A terminal fault the backend dead-letters: an artifact past the plan-sized byte cap can never succeed and re-fetches identical over-cap bytes on every redelivery, so it is not acked (a plain delete would silently discard it on a durable queue) but handed to the queue's deadLetter terminus -- the in-memory backend drops it, a durable queue rides it to the dead-letter queue for forensic retention. Carries the reason.

Retried Text

A transient fault: a fetch failure, or a registry rejection worth retrying. The message is left un-acked so it redelivers. Carries the reason.

Instances

Instances details
Show JobOutcome Source # 
Instance details

Defined in Ecluse.Core.Worker.Job

Eq JobOutcome Source # 
Instance details

Defined in Ecluse.Core.Worker.Job

outcomeOfFetchFault :: ArtifactFetchFault -> JobOutcome Source #

Classify a mirror-artifact fetch fault into a terminal job outcome. An artifact over the plan-sized byte cap is a terminal, dead-lettered fault: it is deterministic in the artifact's own size, so a redelivery re-fetches the same over-cap bytes and fails identically, and it must not silently vanish -- it is handed to the backend's dead-letter terminus (see DeadLettered). Any other fetch fault (an unformable URL, a transport failure) is a transient retry, since a redelivery may succeed.

processJob :: ReceiptHandle -> MirrorJob -> WorkerM JobOutcome Source #

Process one mirror job end to end: probe the mirror target for the job's version (a confirmed-present version is acked outright, the duplicate-suppression short-circuit), then re-evaluate current policy, and only on a current admit fetch the artifact, verify it against the integrity digests of the artifact that re-evaluation re-admitted, and publish it to the mirror target. Returns the JobOutcome that decides ack vs. redeliver.

The presence probe exists for the enqueue-to-availability window: mirroring is demand-driven, so every public-leg admit of a still-unmirrored version enqueues its own job, and a fleet-wide install of a novel version enqueues many. Without the probe each duplicate pays a full artifact download and an integrity recompute before the publish discovers the version is already present (the idempotent already-present answer); with it, a duplicate costs one metadata round trip. The probe is an optimisation, never a gate: it skips only work whose publish would have been that no-op, so the policy re-evaluation below still guards every artifact that actually publishes.

The policy re-evaluation is the ingest-time gate. The version was gated at serve time, but the enqueue-to-process window is asynchronous and unbounded, so policy may have tightened toward deny since (a new denylist entry, a freshly-published advisory, a rule-config change). The worker re-runs the same rules the serve path gates with, over the version resolved through the same single-version fetch-and-project, so a now-denied version is dropped (acked, never published) rather than frozen into the rule-exempt trusted mirror store; a version the upstream has since withdrawn is likewise dropped, while metadata that cannot be re-fetched (or a rule that cannot be computed) leaves the job for redelivery. A current admit carries the re-admitted artifact's integrity digests to the tamper gate, so the fetched bytes are verified against the exact set the integrity floor cleared (the queue payload carries no digest at all): a tampered or corrupt artifact fails the job with no publish, since the mirror is later served without the rules.

The receipt handle is taken so a long publish can extendVisibility to hold the message before its window lapses.

The per-job domain span (the worker tracing port) wraps the whole probe → re-evaluate → fetch → verify → publish, projecting the terminal outcome onto the span so a refused or dropped job is explainable from the trace, and linking back to the request that enqueued the job through the trace context the job carries (jobTraceContext). The span body is discharged to IO through the unlift, so the loop's structured log lines still compose through the ambient katip context.

processBatch :: [QueueMessage] -> WorkerM () Source #

Process one received batch sequentially, so each job gets the full visibility budget rather than competing with its batch-mates for it. A batch is at most the queue's configured batch size (≤ 10), so sequential processing is a deliberate throughput-vs-budget choice, not a scaling bottleneck.

The liveness heartbeat advances after each completed job, not once for the whole batch, so the /livez staleness bound (workerHeartbeatStaleAfter) need only cover one job's worst case rather than a whole sequential batch of large-artifact publishes: a healthy worker mid-batch is not mistaken for a stalled one.

workerPublishVisibilityBudget :: Seconds Source #

The visibility window one publish is given before its message could redeliver mid-write. Sized to comfortably cover a publish of the largest artifact the memory plan's fetch cap admits (the mirror-artifact tenant, at most 512 MiB at its ceiling): even over a slow mirror-target link (a conservative ~2 MiB/s floor) that uploads in well under this, so a successful publish never redelivers mid-flight. A failed publish does not wait this out -- the failure path resets the message to visible at once (see releaseForRetry) -- so the generous hold costs nothing on the retry path; this is the background worker's correct trade (never interrupt a slow success; retry latency on failure does not matter).

The liveness staleness bound (workerHeartbeatStaleAfter) is sized to exceed a fetch and a publish of this budget, so a healthy worker mid-publish is never read as stalled; Ecluse.Worker.LivenessSpec pins that relationship so the two constants cannot drift apart.