@sixb/pipeline-worker 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/errors.d.ts +17 -7
- package/dist/errors.d.ts.map +1 -1
- package/dist/events.d.ts +5 -11
- package/dist/events.d.ts.map +1 -1
- package/dist/execute-run-step.d.ts.map +1 -1
- package/dist/execute-sql-step.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +294 -139
- package/dist/index.js.map +11 -11
- package/dist/run-pipeline-job.d.ts +1 -1
- package/dist/run-pipeline-job.d.ts.map +1 -1
- package/dist/run-step.d.ts.map +1 -1
- package/dist/step-inputs.d.ts +1 -0
- package/dist/step-inputs.d.ts.map +1 -1
- package/dist/types.d.ts +16 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts +11 -6
- package/dist/worker.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/errors.ts +85 -32
- package/src/events.ts +34 -28
- package/src/execute-run-step.ts +13 -3
- package/src/execute-sql-step.ts +39 -7
- package/src/index.ts +2 -1
- package/src/run-pipeline-job.ts +60 -17
- package/src/run-step.ts +59 -15
- package/src/step-inputs.ts +17 -5
- package/src/types.ts +22 -14
- package/src/worker.ts +105 -73
package/src/run-step.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { DatasetDefinition, PipelineDefinition, PipelineStepDefinition } from "@sixb/core"
|
|
2
|
+
import { captureSixbFailure } from "@sixb/core/internal/errors"
|
|
2
3
|
import type { DatasetVersion } from "@sixb/core/lake-storage"
|
|
3
|
-
import type
|
|
4
|
+
import { PIPELINE_RUN_FAILURE_CODES, type PipelineStepRunRecord } from "@sixb/core/storage"
|
|
4
5
|
import {
|
|
6
|
+
createPipelineStepFailure,
|
|
5
7
|
createStepBookkeepingError,
|
|
6
8
|
requireRegisteredDataset,
|
|
7
9
|
statusForFailure,
|
|
8
10
|
throwIfAborted,
|
|
9
|
-
toPipelineRunFailure,
|
|
10
11
|
} from "./errors"
|
|
11
12
|
import { executeRunStep } from "./execute-run-step"
|
|
12
13
|
import { executeSqlStep } from "./execute-sql-step"
|
|
@@ -44,17 +45,37 @@ export async function runStep(input: {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
throwIfAborted(signal)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
let resolvedInputs: readonly ResolvedStepInput[]
|
|
49
|
+
let outputDataset: DatasetDefinition
|
|
50
|
+
try {
|
|
51
|
+
resolvedInputs = await resolveStepInputs({
|
|
52
|
+
runtime,
|
|
53
|
+
pipeline,
|
|
54
|
+
step,
|
|
55
|
+
pipelineRunId: job.id,
|
|
56
|
+
})
|
|
57
|
+
outputDataset = requireRegisteredDataset({
|
|
58
|
+
dataset: runtime.datasets.getById(step.output.id),
|
|
59
|
+
pipelineId: pipeline.id,
|
|
60
|
+
pipelineRunId: job.id,
|
|
61
|
+
stepId: step.id,
|
|
62
|
+
role: "output",
|
|
63
|
+
datasetId: step.output.id,
|
|
64
|
+
})
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
await runtime.lakeStorage.createDataset(outputDataset)
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (statusForFailure(signal, error) === "failed") {
|
|
69
|
+
throw createPipelineStepFailure({
|
|
70
|
+
pipelineId: pipeline.id,
|
|
71
|
+
pipelineRunId: job.id,
|
|
72
|
+
stepId: step.id,
|
|
73
|
+
cause: error,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
throw error
|
|
77
|
+
}
|
|
78
|
+
const inputRefs = resolvedInputs.map((resolved) => resolved.ref)
|
|
58
79
|
|
|
59
80
|
const stepRunId = `${job.id}:step:${stepIndex + 1}:${step.id}`
|
|
60
81
|
const startedStepRun = await runtime.pipelineRunsStorage.startStep({
|
|
@@ -101,8 +122,9 @@ export async function runStep(input: {
|
|
|
101
122
|
} catch (error) {
|
|
102
123
|
throw createStepBookkeepingError({
|
|
103
124
|
pipelineId: pipeline.id,
|
|
125
|
+
pipelineRunId: job.id,
|
|
104
126
|
stepId: step.id,
|
|
105
|
-
|
|
127
|
+
stepRunId,
|
|
106
128
|
version: committedVersion,
|
|
107
129
|
cause: error,
|
|
108
130
|
})
|
|
@@ -116,19 +138,41 @@ export async function runStep(input: {
|
|
|
116
138
|
}
|
|
117
139
|
} catch (error) {
|
|
118
140
|
if (!committedVersion) {
|
|
141
|
+
const status = statusForFailure(signal, error)
|
|
142
|
+
const failureError =
|
|
143
|
+
status === "failed"
|
|
144
|
+
? createPipelineStepFailure({
|
|
145
|
+
pipelineId: pipeline.id,
|
|
146
|
+
pipelineRunId: job.id,
|
|
147
|
+
stepId: step.id,
|
|
148
|
+
stepRunId,
|
|
149
|
+
cause: error,
|
|
150
|
+
})
|
|
151
|
+
: error
|
|
119
152
|
const failedStepRun = await runtime.pipelineRunsStorage
|
|
120
153
|
.finishStep({
|
|
121
154
|
projectId: runtime.id,
|
|
122
155
|
id: stepRunId,
|
|
123
|
-
status
|
|
156
|
+
status,
|
|
124
157
|
rowsWritten,
|
|
125
|
-
error:
|
|
158
|
+
error: captureSixbFailure(failureError, {
|
|
159
|
+
allowedCodes: PIPELINE_RUN_FAILURE_CODES,
|
|
160
|
+
defaultCode: status === "cancelled" ? "runtime.cancelled" : "internal.unexpected",
|
|
161
|
+
details: {
|
|
162
|
+
pipelineId: pipeline.id,
|
|
163
|
+
pipelineRunId: job.id,
|
|
164
|
+
stepId: step.id,
|
|
165
|
+
stepRunId,
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
126
168
|
})
|
|
127
169
|
.catch(() => null)
|
|
128
170
|
|
|
129
171
|
if (failedStepRun) {
|
|
130
172
|
await input.onStepFinished?.(failedStepRun, lifecycleContext)
|
|
131
173
|
}
|
|
174
|
+
|
|
175
|
+
throw failureError
|
|
132
176
|
}
|
|
133
177
|
|
|
134
178
|
throw error
|
package/src/step-inputs.ts
CHANGED
|
@@ -5,12 +5,13 @@ import type {
|
|
|
5
5
|
PipelineStepInput,
|
|
6
6
|
PipelineStepRunContext,
|
|
7
7
|
} from "@sixb/core"
|
|
8
|
+
import { createSixbError } from "@sixb/core/internal/errors"
|
|
8
9
|
import type {
|
|
9
10
|
DatasetVersion,
|
|
10
11
|
DatasetVersionRef,
|
|
11
12
|
ReadDatasetRowsInput,
|
|
12
13
|
} from "@sixb/core/lake-storage"
|
|
13
|
-
import {
|
|
14
|
+
import { requireRegisteredDataset } from "./errors"
|
|
14
15
|
import type { PipelineWorkerContext } from "./types"
|
|
15
16
|
|
|
16
17
|
export interface ResolvedStepInput {
|
|
@@ -24,14 +25,16 @@ export async function resolveStepInputs(input: {
|
|
|
24
25
|
readonly runtime: PipelineWorkerContext
|
|
25
26
|
readonly pipeline: PipelineDefinition
|
|
26
27
|
readonly step: PipelineStepDefinition
|
|
28
|
+
readonly pipelineRunId: string
|
|
27
29
|
}): Promise<readonly ResolvedStepInput[]> {
|
|
28
|
-
const { runtime, pipeline, step } = input
|
|
30
|
+
const { runtime, pipeline, step, pipelineRunId } = input
|
|
29
31
|
const resolved: ResolvedStepInput[] = []
|
|
30
32
|
|
|
31
33
|
for (const [name, declaredDataset] of Object.entries(step.inputs)) {
|
|
32
34
|
const dataset = requireRegisteredDataset({
|
|
33
|
-
dataset: runtime.
|
|
35
|
+
dataset: runtime.datasets.getById(declaredDataset.id),
|
|
34
36
|
pipelineId: pipeline.id,
|
|
37
|
+
pipelineRunId,
|
|
35
38
|
stepId: step.id,
|
|
36
39
|
role: "input",
|
|
37
40
|
name,
|
|
@@ -40,8 +43,17 @@ export async function resolveStepInputs(input: {
|
|
|
40
43
|
|
|
41
44
|
const version = await runtime.lakeStorage.getLatestVersion(dataset.id)
|
|
42
45
|
if (!version) {
|
|
43
|
-
throw
|
|
44
|
-
|
|
46
|
+
throw createSixbError(
|
|
47
|
+
"internal.unexpected",
|
|
48
|
+
`[SixbPipelineWorker] Pipeline '${pipeline.id}' step '${step.id}' input '${name}' dataset '${dataset.id}' has no committed version.`,
|
|
49
|
+
{
|
|
50
|
+
details: {
|
|
51
|
+
pipelineId: pipeline.id,
|
|
52
|
+
pipelineRunId,
|
|
53
|
+
stepId: step.id,
|
|
54
|
+
datasetId: dataset.id,
|
|
55
|
+
},
|
|
56
|
+
}
|
|
45
57
|
)
|
|
46
58
|
}
|
|
47
59
|
|
package/src/types.ts
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
DatasetDefinition,
|
|
3
2
|
DomainEventLog,
|
|
4
3
|
LakeStorage,
|
|
5
|
-
PipelineDefinition,
|
|
6
4
|
Queues,
|
|
5
|
+
SixbDefinitions,
|
|
6
|
+
SixbFailure,
|
|
7
7
|
Storage,
|
|
8
8
|
} from "@sixb/core"
|
|
9
|
-
import type {
|
|
9
|
+
import type { LoggingService } from "@sixb/core/internal/logging"
|
|
10
|
+
import type { PrimitiveExecutionHost } from "@sixb/core/internal/primitive-execution"
|
|
10
11
|
import type { DatasetVersion } from "@sixb/core/lake-storage"
|
|
11
12
|
import type {
|
|
13
|
+
PipelineRunFailureCode,
|
|
12
14
|
PipelineRunRecord,
|
|
13
15
|
PipelineRunStorage,
|
|
14
16
|
PipelineStepRunRecord,
|
|
15
17
|
} from "@sixb/core/storage"
|
|
16
18
|
|
|
17
|
-
export type PipelineLogSession = ReturnType<
|
|
19
|
+
export type PipelineLogSession = ReturnType<LoggingService["startExecution"]>
|
|
18
20
|
|
|
19
21
|
export interface PipelineWorkerContext {
|
|
20
22
|
readonly id: string
|
|
21
23
|
readonly pipelineRunsStorage: PipelineRunStorage
|
|
22
24
|
readonly lakeStorage: LakeStorage
|
|
23
|
-
readonly
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
readonly logging?: LoggingService
|
|
26
|
+
readonly datasets: Pick<SixbDefinitions["datasets"], "getById">
|
|
27
|
+
readonly pipelines: Pick<SixbDefinitions["pipelines"], "getById">
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
export interface
|
|
30
|
+
export interface PipelineWorkerHost extends PrimitiveExecutionHost {
|
|
29
31
|
readonly id: string
|
|
30
32
|
readonly events?: DomainEventLog
|
|
31
|
-
readonly
|
|
33
|
+
readonly logging?: LoggingService
|
|
32
34
|
readonly lakeStorage: LakeStorage
|
|
33
35
|
readonly queues: Queues
|
|
34
36
|
readonly storage: Storage
|
|
35
|
-
|
|
36
|
-
getPipelineById(pipelineId: string): PipelineDefinition | null
|
|
37
|
-
getDatasetById(datasetId: string): DatasetDefinition | null
|
|
37
|
+
readonly definitions: Pick<SixbDefinitions, "pipelines" | "datasets">
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export interface PipelineJob {
|
|
@@ -44,9 +44,11 @@ export interface PipelineJob {
|
|
|
44
44
|
|
|
45
45
|
export interface RunPipelineJobInput {
|
|
46
46
|
readonly runtime: PipelineWorkerContext
|
|
47
|
-
|
|
47
|
+
/** Durable run loaded before the execution scope is restored. */
|
|
48
|
+
readonly run: PipelineRunRecord
|
|
48
49
|
readonly signal?: AbortSignal
|
|
49
50
|
readonly onRunStarted?: PipelineRunStartedHandler
|
|
51
|
+
readonly onRunFinished?: PipelineRunFinishedHandler
|
|
50
52
|
readonly onRunFailed?: PipelineRunFailedHandler
|
|
51
53
|
readonly onStepStarted?: PipelineStepStartedHandler
|
|
52
54
|
readonly onStepFinished?: PipelineStepFinishedHandler
|
|
@@ -57,7 +59,13 @@ export type PipelineStepCommittedHandler = (result: PipelineStepRunResult) => Pr
|
|
|
57
59
|
|
|
58
60
|
export type PipelineRunStartedHandler = (run: PipelineRunRecord) => Promise<void> | void
|
|
59
61
|
|
|
60
|
-
export type
|
|
62
|
+
export type PipelineRunFinishedHandler = (run: PipelineRunRecord) => Promise<void> | void
|
|
63
|
+
|
|
64
|
+
export type PipelineRunFailedHandler = (
|
|
65
|
+
error: unknown,
|
|
66
|
+
run: PipelineRunRecord,
|
|
67
|
+
failure: SixbFailure<PipelineRunFailureCode>
|
|
68
|
+
) => void
|
|
61
69
|
|
|
62
70
|
export interface PipelineStepLifecycleContext {
|
|
63
71
|
readonly stepIndex: number
|
package/src/worker.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { reportRunFailure } from "@sixb/core/internal/error-reporting"
|
|
2
|
+
import { bindDurablePrimitiveExecution } from "@sixb/core/internal/primitive-execution"
|
|
2
3
|
import type { QueueWorkerFailureDecision } from "@sixb/core/internal/workers"
|
|
3
4
|
import { QueueWorker } from "@sixb/core/internal/workers"
|
|
4
5
|
import type { ClaimedQueueJob, PipelineRunRequestedQueueJob } from "@sixb/core/queues"
|
|
5
|
-
import type
|
|
6
|
+
import { PIPELINE_RUN_FAILURE_CODES, type PipelineRunStorage } from "@sixb/core/storage"
|
|
6
7
|
import {
|
|
7
8
|
emitDatasetVersionCommitted,
|
|
8
9
|
emitPipelineRunFinished,
|
|
@@ -11,35 +12,40 @@ import {
|
|
|
11
12
|
emitPipelineRunStepStarted,
|
|
12
13
|
} from "./events"
|
|
13
14
|
import { PipelineRunAlreadyStartedError, runPipelineJob } from "./run-pipeline-job"
|
|
14
|
-
import type {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class PipelineWorker extends QueueWorker<
|
|
15
|
+
import type { PipelineJob, PipelineWorkerContext, PipelineWorkerHost } from "./types"
|
|
16
|
+
|
|
17
|
+
export interface PipelineWorkerOptions {
|
|
18
|
+
/** Maximum pipeline run jobs this worker claims and executes at once. Defaults to 1. */
|
|
19
|
+
readonly concurrency?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class PipelineWorker extends QueueWorker<
|
|
23
|
+
PipelineRunRequestedQueueJob,
|
|
24
|
+
typeof PIPELINE_RUN_FAILURE_CODES
|
|
25
|
+
> {
|
|
22
26
|
private readonly context: PipelineWorkerContext
|
|
23
|
-
private readonly
|
|
27
|
+
private readonly host: PipelineWorkerHost
|
|
24
28
|
|
|
25
|
-
constructor(
|
|
26
|
-
if (
|
|
29
|
+
constructor(host: PipelineWorkerHost, options: PipelineWorkerOptions = {}) {
|
|
30
|
+
if (host.definitions.pipelines.list().length === 0) {
|
|
27
31
|
throw new Error("[SixbPipelineWorker] No pipeline definitions are registered.")
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
const pipelineRunsStorage =
|
|
34
|
+
const pipelineRunsStorage = host.storage.pipelineRuns
|
|
31
35
|
if (!pipelineRunsStorage) {
|
|
32
36
|
throw new Error("[SixbPipelineWorker] Pipeline workers require storage.pipelineRuns support.")
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
super({
|
|
36
|
-
projectId:
|
|
37
|
-
queue:
|
|
38
|
-
|
|
40
|
+
projectId: host.id,
|
|
41
|
+
queue: host.queues.pipelines,
|
|
42
|
+
failureCodes: PIPELINE_RUN_FAILURE_CODES,
|
|
43
|
+
workerId: `pipeline-worker-${host.id}`,
|
|
44
|
+
claimLimit: options.concurrency,
|
|
39
45
|
})
|
|
40
46
|
|
|
41
|
-
this.context = buildPipelineContext(
|
|
42
|
-
this.
|
|
47
|
+
this.context = buildPipelineContext(host, pipelineRunsStorage)
|
|
48
|
+
this.host = host
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
protected async execute(
|
|
@@ -47,106 +53,132 @@ export class PipelineWorker extends QueueWorker<PipelineRunRequestedQueueJob> {
|
|
|
47
53
|
signal: AbortSignal
|
|
48
54
|
): Promise<void> {
|
|
49
55
|
const { job } = claimed
|
|
56
|
+
const pipelineRuns = this.host.storage.pipelineRuns
|
|
57
|
+
if (!pipelineRuns) {
|
|
58
|
+
throw new Error("[SixbPipelineWorker] Pipeline workers require storage.pipelineRuns support.")
|
|
59
|
+
}
|
|
60
|
+
const run = await pipelineRuns.getById({ projectId: this.host.id, id: job.payload.runId })
|
|
61
|
+
if (!run) {
|
|
62
|
+
throw new Error(`[SixbPipelineWorker] Pipeline run '${job.payload.runId}' was not found.`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const durableExecution = await this.host.storage.executions.getById({
|
|
66
|
+
projectId: this.host.id,
|
|
67
|
+
id: run.executionId,
|
|
68
|
+
})
|
|
69
|
+
if (!durableExecution) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`[SixbPipelineWorker] Pipeline run '${run.id}' references missing execution '${run.executionId}'.`
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
50
75
|
const pipelineJob: PipelineJob = {
|
|
51
|
-
id:
|
|
52
|
-
pipelineId:
|
|
76
|
+
id: run.id,
|
|
77
|
+
pipelineId: run.pipelineId,
|
|
53
78
|
}
|
|
79
|
+
// Pipeline steps currently use host-owned lake and run storage directly. Binding still happens
|
|
80
|
+
// here so the durable execution and its trusted primitive authority are validated before work.
|
|
81
|
+
bindDurablePrimitiveExecution(this.host, {
|
|
82
|
+
execution: durableExecution,
|
|
83
|
+
primitive: { kind: "pipeline", id: run.pipelineId, runId: run.id },
|
|
84
|
+
})
|
|
54
85
|
|
|
55
|
-
let result: PipelineRunResult
|
|
56
86
|
try {
|
|
57
|
-
|
|
87
|
+
await runPipelineJob({
|
|
58
88
|
runtime: this.context,
|
|
59
|
-
|
|
89
|
+
run,
|
|
60
90
|
signal,
|
|
61
|
-
onRunStarted: (run) =>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
onRunStarted: (run) =>
|
|
92
|
+
emitPipelineRunStarted(this.host.events, run, durableExecution.correlationId),
|
|
93
|
+
onRunFinished: (run) =>
|
|
94
|
+
emitPipelineRunFinished(this.host.events, run, durableExecution.correlationId),
|
|
95
|
+
onRunFailed: (error, run, failure) => {
|
|
96
|
+
reportRunFailure(this.host, error, {
|
|
97
|
+
projectId: this.host.id,
|
|
66
98
|
attempt: job.attempt,
|
|
99
|
+
runKind: "pipeline",
|
|
67
100
|
run: {
|
|
68
|
-
kind: "pipeline",
|
|
69
101
|
runId: run.id,
|
|
70
102
|
pipelineId: run.pipelineId,
|
|
71
103
|
},
|
|
104
|
+
failure,
|
|
72
105
|
})
|
|
73
106
|
},
|
|
74
107
|
onStepStarted: (step, context) =>
|
|
75
|
-
emitPipelineRunStepStarted(
|
|
108
|
+
emitPipelineRunStepStarted(
|
|
109
|
+
this.host.events,
|
|
110
|
+
step,
|
|
111
|
+
context,
|
|
112
|
+
durableExecution.correlationId
|
|
113
|
+
),
|
|
76
114
|
onStepFinished: (step, context) =>
|
|
77
|
-
emitPipelineRunStepFinished(
|
|
78
|
-
|
|
115
|
+
emitPipelineRunStepFinished(
|
|
116
|
+
this.host.events,
|
|
117
|
+
step,
|
|
118
|
+
context,
|
|
119
|
+
durableExecution.correlationId
|
|
120
|
+
),
|
|
121
|
+
onStepCommitted: (step) =>
|
|
122
|
+
emitDatasetVersionCommitted(
|
|
123
|
+
this.host.events,
|
|
124
|
+
pipelineJob,
|
|
125
|
+
step,
|
|
126
|
+
durableExecution.correlationId
|
|
127
|
+
),
|
|
79
128
|
})
|
|
80
129
|
} catch (error) {
|
|
81
130
|
if (error instanceof PipelineRunAlreadyStartedError) return
|
|
82
131
|
throw error
|
|
83
132
|
}
|
|
84
|
-
|
|
85
|
-
await emitPipelineRunFinished(this.sixb.events, {
|
|
86
|
-
id: pipelineJob.id,
|
|
87
|
-
pipelineId: pipelineJob.pipelineId,
|
|
88
|
-
status: "succeeded",
|
|
89
|
-
datasetId: result.version?.datasetId,
|
|
90
|
-
versionId: result.version?.versionId,
|
|
91
|
-
})
|
|
92
133
|
}
|
|
93
134
|
|
|
94
135
|
protected override async onExecutionError(
|
|
95
136
|
claimed: ClaimedQueueJob<PipelineRunRequestedQueueJob>,
|
|
96
137
|
_error: unknown
|
|
97
|
-
): Promise<QueueWorkerFailureDecision
|
|
98
|
-
const { job } = claimed
|
|
99
|
-
await emitPipelineRunFinished(this.sixb.events, {
|
|
100
|
-
id: job.payload.runId ?? `${job.id}:attempt:${job.attempt}`,
|
|
101
|
-
pipelineId: job.payload.pipelineId,
|
|
102
|
-
status: "failed",
|
|
103
|
-
})
|
|
104
|
-
|
|
138
|
+
): Promise<QueueWorkerFailureDecision<(typeof PIPELINE_RUN_FAILURE_CODES)[number]>> {
|
|
105
139
|
// Pipeline runs are not all-or-nothing in V1. A previous step may have committed an append
|
|
106
140
|
// output, so failed pipeline jobs must not be retried automatically.
|
|
107
|
-
|
|
141
|
+
const run = await this.host.storage.pipelineRuns?.getById({
|
|
142
|
+
projectId: this.host.id,
|
|
143
|
+
id: claimed.job.payload.runId,
|
|
144
|
+
})
|
|
145
|
+
return run?.status === "failed" && run.error
|
|
146
|
+
? { kind: "fail", failure: run.error }
|
|
147
|
+
: { kind: "fail" }
|
|
108
148
|
}
|
|
109
149
|
|
|
110
150
|
protected override async onAbortError(
|
|
111
151
|
claimed: ClaimedQueueJob<PipelineRunRequestedQueueJob>,
|
|
112
152
|
_error: unknown
|
|
113
|
-
): Promise<QueueWorkerFailureDecision
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
id: job.payload.runId
|
|
117
|
-
|
|
118
|
-
}
|
|
153
|
+
): Promise<QueueWorkerFailureDecision<(typeof PIPELINE_RUN_FAILURE_CODES)[number]>> {
|
|
154
|
+
const run = await this.host.storage.pipelineRuns?.getById({
|
|
155
|
+
projectId: this.host.id,
|
|
156
|
+
id: claimed.job.payload.runId,
|
|
157
|
+
})
|
|
158
|
+
if (!run) return { kind: "fail" }
|
|
119
159
|
|
|
120
|
-
if (!(await hasCommittedStep(this.context,
|
|
160
|
+
if (!(await hasCommittedStep(this.context, run.id))) {
|
|
121
161
|
return { kind: "retry" }
|
|
122
162
|
}
|
|
123
163
|
|
|
124
164
|
// After a step commit, rerunning the whole pipeline could duplicate append outputs.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
status: "cancelled",
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
return { kind: "fail" }
|
|
165
|
+
return run.status === "cancelled" && run.error
|
|
166
|
+
? { kind: "fail", failure: run.error }
|
|
167
|
+
: { kind: "fail" }
|
|
132
168
|
}
|
|
133
169
|
}
|
|
134
170
|
|
|
135
171
|
function buildPipelineContext(
|
|
136
|
-
|
|
172
|
+
host: PipelineWorkerHost,
|
|
137
173
|
pipelineRunsStorage: PipelineRunStorage
|
|
138
174
|
): PipelineWorkerContext {
|
|
139
175
|
return {
|
|
140
|
-
id:
|
|
176
|
+
id: host.id,
|
|
141
177
|
pipelineRunsStorage,
|
|
142
|
-
lakeStorage:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
},
|
|
147
|
-
getDatasetById(datasetId) {
|
|
148
|
-
return sixb.getDatasetById(datasetId)
|
|
149
|
-
},
|
|
178
|
+
lakeStorage: host.lakeStorage,
|
|
179
|
+
logging: host.logging,
|
|
180
|
+
pipelines: host.definitions.pipelines,
|
|
181
|
+
datasets: host.definitions.datasets,
|
|
150
182
|
}
|
|
151
183
|
}
|
|
152
184
|
|