-
Notifications
You must be signed in to change notification settings - Fork 662
fix(worker): propagate process <job_name> span's metadata into processor function so child spans are correctly nested
#4064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -436,7 +436,27 @@ export class Worker< | |||||||||||||||||||||||||||||||||||||||||||||||||
| job: Job<DataType, ResultType, NameType>, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| token: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| signal?: AbortSignal, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| srcPropagationMetadata?: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<ResultType> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Need to overwrite telemetry.metadata to be able to restore | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // propagation context in a worker thread/fork. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // This is only necessary for sandboxed workers because OTEL doesn't | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // propagate context cross-threads/cross-processes. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this.childPool && job.opts.telemetry) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return this.processFn( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| job.cloneWithOpts({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| telemetry: { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ...job.opts.telemetry, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata: job.opts.telemetry.omitContext | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ? undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||
| : srcPropagationMetadata, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| : srcPropagationMetadata, | |
| : srcPropagationMetadata | |
| ? srcPropagationMetadata | |
| : job.opts.telemetry.metadata, |
Copilot
AI
Apr 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the sandboxed path, passing a cloned Job instance into processFn changes which Job object the sandbox bridge operates on (see sandbox.ts: progress/log/move commands call methods on the job instance passed into sandbox()). This can make progress events emit with the cloned job while completed/failed emit with the original job, and can desync in-memory fields between the two instances. Prefer keeping the original job instance for sandbox communication and only overriding the telemetry metadata in the serialized payload sent to the child.
| return this.processFn( | |
| job.cloneWithOpts({ | |
| telemetry: { | |
| ...job.opts.telemetry, | |
| metadata: job.opts.telemetry.omitContext | |
| ? undefined | |
| : srcPropagationMetadata, | |
| }, | |
| }), | |
| token, | |
| signal, | |
| ); | |
| const telemetry = job.opts.telemetry; | |
| const originalMetadata = telemetry.metadata; | |
| telemetry.metadata = telemetry.omitContext | |
| ? undefined | |
| : srcPropagationMetadata; | |
| try { | |
| return this.processFn(job, token, signal); | |
| } finally { | |
| telemetry.metadata = originalMetadata; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is actually a big problem, which makes me think that cloneWithOpts might be pointless - since I want to avoid cloning a job and then cloning that job back into original-ish job. I think it's way better to simply break the immutability of job.opts in this one case and re-assign the metadata back so it's all encapsulated in one place.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| /** | ||
| * A processor file to be used in tests. | ||
| * | ||
| */ | ||
| 'use strict'; | ||
|
|
||
| module.exports = function (job) { | ||
| return job.opts.tm; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1987,4 +1987,77 @@ describe('Job', () => { | |
| await worker.close(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('.cloneWithOpts', () => { | ||
| it('should clone one-off debounced and delayed job', async () => { | ||
| const sourceJob = await Job.create( | ||
| queue, | ||
| 'test_job', | ||
| { foo: 'bar' }, | ||
| { | ||
| jobId: 'custom_job_id', | ||
| delay: 1000, | ||
| deduplication: { id: 'dedup-id' }, | ||
| debounce: { id: 'debounce-id' }, | ||
| backoff: { type: 'fixed', jitter: 0 }, | ||
| telemetry: { | ||
| metadata: 'source_metadata', | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| const clonedJob = sourceJob.cloneWithOpts({ | ||
| telemetry: { | ||
| metadata: 'target_metadata', | ||
| }, | ||
| }); | ||
|
|
||
| expect(clonedJob.asJSON()).toEqual({ | ||
| ...sourceJob.asJSON(), | ||
| opts: { | ||
| ...sourceJob.asJSON().opts, | ||
| // timestamp for cloned job is passed as opts, | ||
| // whereas for sourceJob it's autogenerated hence the difference | ||
| timestamp: sourceJob.timestamp, | ||
| tm: clonedJob.asJSON().opts.tm, | ||
| }, | ||
| }); | ||
|
Comment on lines
+1991
to
+2024
|
||
| }); | ||
|
|
||
| it('should clone repeatable job with parent', async () => { | ||
| const parentJob = await Job.create(queue, 'parent', {}); | ||
|
|
||
| const sourceJob = await Job.create( | ||
| queue, | ||
| 'test_job', | ||
| { foo: 'bar' }, | ||
| { | ||
| jobId: 'custom_job_id', | ||
| parent: { id: parentJob.id!, queue: `${prefix}:${queueName}` }, | ||
| repeat: { every: 200 }, | ||
| removeDependencyOnFailure: true, | ||
| telemetry: { | ||
| metadata: 'source_metadata', | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| const clonedJob = sourceJob.cloneWithOpts({ | ||
| telemetry: { | ||
| metadata: 'target_metadata', | ||
| }, | ||
| }); | ||
|
|
||
| expect(clonedJob.asJSON()).toEqual({ | ||
| ...sourceJob.asJSON(), | ||
| opts: { | ||
| ...sourceJob.asJSON().opts, | ||
| // timestamp for cloned job is passed as opts, | ||
| // whereas for sourceJob it's autogenerated hence the difference | ||
| timestamp: sourceJob.timestamp, | ||
| tm: clonedJob.asJSON().opts.tm, | ||
| }, | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,7 +10,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from 'vitest'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { v4 } from 'uuid'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { FlowProducer, JobScheduler, Queue, Worker } from '../src/classes'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { FlowProducer, Job, JobScheduler, Queue, Worker } from '../src/classes'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { removeAllQueueData } from '../src/utils'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Telemetry, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,6 +29,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '../src/interfaces'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as sinon from 'sinon'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { SpanKind, TelemetryAttributes, MetricNames } from '../src/enums'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { pathToFileURL } from 'url'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('Telemetry', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type ExtendedException = Exception & { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -394,6 +395,65 @@ describe('Telemetry', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await worker.close(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should correctly interact with telemetry when processing a sandboxed job', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const processFile = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| __dirname + '/fixtures/fixture_processor_custom_span.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const processUrl = pathToFileURL(processFile); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const worker = new Worker(queueName, processUrl, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connection, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| telemetry: telemetryClient, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: 'testWorker', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prefix, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useWorkerThreads: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const completing = new Promise<string>((resolve, reject) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| worker.on('completed', async (_, value: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolve(value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reject(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const startSpanSpy = sinon.spy(telemetryClient.tracer, 'startSpan'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const job = await queue.add('testJob', { foo: 'bar' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const processContextMetadata = JSON.parse(await completing); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const addSpan = startSpanSpy.returnValues[0] as MockSpan; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(addSpan).toBeInstanceOf(MockSpan); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(addSpan.name).toBe(`add ${queueName}.${job.name}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(addSpan.options?.kind).toBe(SpanKind.PRODUCER); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(addSpan.attributes[TelemetryAttributes.QueueName]).toBe( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queue.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(addSpan.attributes[TelemetryAttributes.QueueOperation]).toBe( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'add', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(addSpan.attributes[TelemetryAttributes.JobName]).toBe(job.name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(addSpan.attributes[TelemetryAttributes.JobId]).toBe(job.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const processSpan = startSpanSpy.returnValues[1] as MockSpan; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(processSpan).toBeInstanceOf(MockSpan); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(processSpan.name).toBe(`process ${queueName}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(processSpan.options?.kind).toBe(SpanKind.CONSUMER); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(processSpan.attributes[TelemetryAttributes.WorkerId]).toBe( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| worker.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(processSpan.attributes[TelemetryAttributes.WorkerName]).toBe( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'testWorker', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(processSpan.attributes[TelemetryAttributes.JobId]).toBe(job.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(processContextMetadata['getMetadata_span']).toBe(processSpan.name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+426
to
+452
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const addSpan = startSpanSpy.returnValues[0] as MockSpan; | |
| expect(addSpan).toBeInstanceOf(MockSpan); | |
| expect(addSpan.name).toBe(`add ${queueName}.${job.name}`); | |
| expect(addSpan.options?.kind).toBe(SpanKind.PRODUCER); | |
| expect(addSpan.attributes[TelemetryAttributes.QueueName]).toBe( | |
| queue.name, | |
| ); | |
| expect(addSpan.attributes[TelemetryAttributes.QueueOperation]).toBe( | |
| 'add', | |
| ); | |
| expect(addSpan.attributes[TelemetryAttributes.JobName]).toBe(job.name); | |
| expect(addSpan.attributes[TelemetryAttributes.JobId]).toBe(job.id); | |
| const processSpan = startSpanSpy.returnValues[1] as MockSpan; | |
| expect(processSpan).toBeInstanceOf(MockSpan); | |
| expect(processSpan.name).toBe(`process ${queueName}`); | |
| expect(processSpan.options?.kind).toBe(SpanKind.CONSUMER); | |
| expect(processSpan.attributes[TelemetryAttributes.WorkerId]).toBe( | |
| worker.id, | |
| ); | |
| expect(processSpan.attributes[TelemetryAttributes.WorkerName]).toBe( | |
| 'testWorker', | |
| ); | |
| expect(processSpan.attributes[TelemetryAttributes.JobId]).toBe(job.id); | |
| expect(processContextMetadata['getMetadata_span']).toBe(processSpan.name); | |
| const spans = startSpanSpy.returnValues as MockSpan[]; | |
| const addSpan = spans.find( | |
| span => span.name === `add ${queueName}.${job.name}`, | |
| ); | |
| expect(addSpan).toBeInstanceOf(MockSpan); | |
| expect(addSpan?.name).toBe(`add ${queueName}.${job.name}`); | |
| expect(addSpan?.options?.kind).toBe(SpanKind.PRODUCER); | |
| expect(addSpan?.attributes[TelemetryAttributes.QueueName]).toBe( | |
| queue.name, | |
| ); | |
| expect(addSpan?.attributes[TelemetryAttributes.QueueOperation]).toBe( | |
| 'add', | |
| ); | |
| expect(addSpan?.attributes[TelemetryAttributes.JobName]).toBe(job.name); | |
| expect(addSpan?.attributes[TelemetryAttributes.JobId]).toBe(job.id); | |
| const processSpan = spans.find( | |
| span => span.name === `process ${queueName}`, | |
| ); | |
| expect(processSpan).toBeInstanceOf(MockSpan); | |
| expect(processSpan?.name).toBe(`process ${queueName}`); | |
| expect(processSpan?.options?.kind).toBe(SpanKind.CONSUMER); | |
| expect(processSpan?.attributes[TelemetryAttributes.WorkerId]).toBe( | |
| worker.id, | |
| ); | |
| expect(processSpan?.attributes[TelemetryAttributes.WorkerName]).toBe( | |
| 'testWorker', | |
| ); | |
| expect(processSpan?.attributes[TelemetryAttributes.JobId]).toBe(job.id); | |
| expect(processContextMetadata['getMetadata_span']).toBe(processSpan?.name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cloneWithOptscurrently constructs a brand-newJobinstance vianew Job(...), which reinitializes runtime state (e.g.progress,attemptsMade,attemptsStarted,stalledCounter,processedOn,finishedOn,failedReason,stacktrace, etc.). Since this method is now used for sandbox processing, the JSON sent to the child (viaasJSONSandbox()) can lose these fields, changing observable behavior inside processors (and potentially retry/attempt logic). Consider cloning by copying the existing instance fields and only replacingopts(or provide an API to generateasJSONSandbox()with overridden opts) so the cloned job preserves all current job state.