Skip to content

fix(core): support manual span parenting on runtimes without native enterWith - #9445

Closed
bm1549 wants to merge 2 commits into
brian.marks/cf-wrangler-cifrom
brian.marks/cf-manual-parenting
Closed

fix(core): support manual span parenting on runtimes without native enterWith#9445
bm1549 wants to merge 2 commits into
brian.marks/cf-wrangler-cifrom
brian.marks/cf-manual-parenting

Conversation

@bm1549

@bm1549 bm1549 commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Enables manual span parenting on runtimes that lack native AsyncLocalStorage.prototype.enterWith (Cloudflare Workers / workerd). Scope.activate() (used by tracer.trace()/tracer.wrap() and nested manual spans) previously set context via enterWith(), which workerd doesn't implement — so only flat, non-activated spans worked there. Since every scope.js activate/bind call site has a bounding callback, the fallback uses the scoped legacyStorage.run(store, callback) instead:

  • datadog-core gains a hasNativeEnterWith capability probe (feature-detected once at module load).
  • scope.js selects the activation strategy once at load — the existing enterWith+finally path when native enterWith is present (Node: byte-identical behavior), the run()-based path when it's absent (workerd). No per-activate() feature-detect on the hot path.
  • The Cloudflare Workers acceptance test now asserts a real parent/child trace (child.parentSpanId === parent.spanId, same traceId), verified in real workerd.

Motivation

Flat spans already worked in workerd (prior PRs in this effort); this makes nested manual tracingtracer.trace('parent', () => tracer.trace('child', ...)) — work too, which is how a Workers app instruments itself. Part of the Cloudflare Workers support effort (#1892).

Additional Notes

  • Stacked PR — base branch is brian.marks/cf-wrangler-ci (test(core): add Cloudflare Workers (workerd) acceptance test #9442). Review the 2 commits unique to this branch.
  • semver-patch — no public API change; Node context propagation is unchanged (the run() path is taken only when native enterWith is absent; the hasNativeEnterWith === true case is directly asserted in storage.spec.js as a regression tripwire).
  • Known limitation (out of scope, blocked upstream): automatic plugin instrumentation parenting still doesn't work in workerd — the plugin layer sets context via enterWith with no bounding callback, which can't be expressed via run(). workerd omits enterWith deliberately/permanently (workerd PR Support for sampling priority #208); the eventual fix is TC39 AsyncContext, not yet shipped. This PR covers manual instrumentation only.

bm1549 and others added 2 commits July 20, 2026 16:06
…nterWith()

workerd implements AsyncLocalStorage.run()/getStore() but throws on the
imperative enterWith()/disable(), so scope.js's activate() (and thus any
tracer.trace()/tracer.wrap()/nested manual span) threw on Cloudflare
Workers — only flat, non-activated spans worked.

Every activate()/bind() call site has a bounding callback, so the
enterWith()+finally-restore path can be swapped for a scoped
storage.run() where enterWith() isn't available. datadog-core/src/storage.js
now feature-detects `hasNativeEnterWith` once at load (independent of the
existing isACFActive probe, since workerd is treated as ACF-active but still
lacks enterWith()), and scope.js picks between the two activate()
implementations once at module load, so there is no per-call feature-detect
on the hot path. The native-enterWith path is unchanged, so Node's context
propagation is unaffected.

This does not fix automatic plugin instrumentation, which calls enterWith()
imperatively outside of any bounding callback — that remains a documented
limitation blocked on workerd's own omission of enterWith().

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Upgrade the wrangler acceptance test to prove scope.js's storage.run()-based
fallback (previous commit) actually parents spans inside real workerd, not
just in Node unit tests with a stubbed capability. The worker now also
handles a /parented route that nests tracer.trace() calls; the test asserts
the FakeAgent's OTLP receiver captured both spans in the same trace with the
child's parentSpanId equal to the parent's spanId. The original flat-span
assertion is unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bm1549 bm1549 added semver-patch AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos labels Jul 20, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Overall package size

Self size: 6.81 MB
Deduped: 7.47 MB
No deduping: 7.47 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.1 | 122.62 kB | 438.86 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@pr-commenter

pr-commenter Bot commented Jul 20, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-07-20 20:21:51

Comparing candidate commit b894da1 in PR branch brian.marks/cf-manual-parenting with baseline commit 7fc73ff in branch brian.marks/cf-wrangler-ci.

📊 Benchmarking dashboard

Found 0 performance improvements and 11 performance regressions! Performance is the same for 2313 metrics, 34 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:plugin-grpc-mixed-24

  • 🟥 cpu_user_time [+79.617ms; +310.896ms] or [+2.278%; +8.893%]
  • 🟥 execution_time [+80.923ms; +309.998ms] or [+2.277%; +8.722%]
  • 🟥 instructions [+1.1G instructions; +2.9G instructions] or [+2.321%; +6.187%]
  • 🟥 throughput [-1345021.350op/s; -422919.638op/s] or [-8.773%; -2.759%]

scenario:plugin-grpc-mixed-26

  • 🟥 instructions [+1.1G instructions; +2.7G instructions] or [+2.282%; +5.844%]

scenario:plugin-grpc-unary-24

  • 🟥 cpu_user_time [+87.456ms; +217.955ms] or [+2.618%; +6.525%]
  • 🟥 execution_time [+91.873ms; +228.206ms] or [+2.701%; +6.709%]
  • 🟥 instructions [+1.2G instructions; +2.8G instructions] or [+2.622%; +6.247%]
  • 🟥 throughput [-1049331.888op/s; -425905.667op/s] or [-6.703%; -2.721%]

scenario:plugin-grpc-unary-26

  • 🟥 instructions [+1.1G instructions; +2.6G instructions] or [+2.420%; +5.801%]

scenario:tracing-channel-command-24

  • 🟥 instructions [+268.4M instructions; +700.3M instructions] or [+5.197%; +13.559%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:appsec-appsec-enabled-24

  • unstable execution_time [-210281.581µs; +208638.681µs] or [-7.905%; +7.843%]

scenario:appsec-appsec-enabled-26

  • unstable execution_time [-237392.981µs; +236381.914µs] or [-9.288%; +9.249%]

scenario:appsec-appsec-enabled-with-attacks-24

  • unstable execution_time [-159.858ms; +162.863ms] or [-5.180%; +5.278%]

scenario:appsec-appsec-enabled-with-attacks-26

  • unstable execution_time [-187.907ms; +193.336ms] or [-6.486%; +6.673%]

scenario:appsec-control-20

  • unstable execution_time [-117.893ms; +129.412ms] or [-7.125%; +7.821%]

scenario:appsec-control-24

  • unstable execution_time [-110.875ms; +114.198ms] or [-8.916%; +9.183%]

scenario:appsec-control-26

  • unstable execution_time [-127.452ms; +114.107ms] or [-10.379%; +9.292%]

scenario:appsec-iast-no-vulnerability-control-20

  • unstable execution_time [-13860.948µs; +14095.215µs] or [-5.382%; +5.473%]

scenario:appsec-iast-no-vulnerability-iast-enabled-always-active-20

  • unstable execution_time [-14.785ms; +20.652ms] or [-5.700%; +7.962%]

scenario:appsec-iast-with-vulnerability-control-20

  • unstable execution_time [-31.449ms; +25.274ms] or [-5.789%; +4.652%]

scenario:appsec-iast-with-vulnerability-iast-enabled-default-config-20

  • unstable execution_time [-30.571ms; +25.176ms] or [-5.548%; +4.569%]

scenario:debugger-line-probe-with-snapshot-minimal-26

  • unstable cpu_user_time [-3634.873ms; +3627.695ms] or [-35.258%; +35.188%]
  • unstable execution_time [-3657.576ms; +3688.780ms] or [-33.041%; +33.323%]
  • unstable instructions [-32240.0M instructions; +32215.7M instructions] or [-37.244%; +37.216%]
  • unstable max_rss_usage [-11323.939KB; +11407.139KB] or [-6.980%; +7.032%]
  • unstable throughput [-730.563op/s; +705.427op/s] or [-23.754%; +22.937%]

scenario:debugger-line-probe-without-snapshot-26

  • unstable cpu_user_time [-2613.520ms; +4141.139ms] or [-27.355%; +43.345%]
  • unstable execution_time [-2621.450ms; +4143.428ms] or [-25.516%; +40.331%]
  • unstable instructions [-23.2G instructions; +36.9G instructions] or [-29.089%; +46.357%]
  • unstable max_rss_usage [-9.023MB; +13.576MB] or [-5.666%; +8.525%]
  • unstable throughput [-814.148op/s; +518.544op/s] or [-25.240%; +16.076%]

scenario:dogstatsd-aggregated-20

  • unstable cpu_usage_percentage [-8.350%; +5.483%]
  • unstable execution_time [-107.958ms; +186.856ms] or [-8.320%; +14.400%]
  • unstable throughput [-1067445.821op/s; +607142.975op/s] or [-9.113%; +5.183%]

scenario:dogstatsd-with-tags-20

  • unstable cpu_user_time [-447.627ms; +207.014ms] or [-9.401%; +4.348%]
  • unstable execution_time [-452.290ms; +210.768ms] or [-9.350%; +4.357%]
  • unstable throughput [-75082.069op/s; +160188.734op/s] or [-4.325%; +9.228%]

scenario:plugin-graphql-long-with-depth-off-20

  • unstable max_rss_usage [-5.780MB; +10.031MB] or [-4.493%; +7.799%]

scenario:plugin-pg-service-26

  • unstable execution_time [-42.310ms; +78.822ms] or [-4.781%; +8.907%]
  • unstable throughput [-483575.992op/s; +248113.780op/s] or [-7.039%; +3.612%]

scenario:test-optimization-large-suite-20

  • unstable max_rss_usage [-5516.941KB; +5166.274KB] or [-6.866%; +6.430%]

scenario:tracing-channel-command-24

  • unstable cpu_user_time [+26.132ms; +78.972ms] or [+5.654%; +17.086%]
  • unstable execution_time [+27.224ms; +79.414ms] or [+5.872%; +17.128%]
  • unstable throughput [-1336740.173op/s; -482265.835op/s] or [-17.171%; -6.195%]

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Jul 20, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 98.34% (+0.00%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: b894da1 | Docs | Datadog PR Page | Give us feedback!

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.34%. Comparing base (7fc73ff) to head (b894da1).

Additional details and impacted files
@@                      Coverage Diff                       @@
##           brian.marks/cf-wrangler-ci    #9445      +/-   ##
==============================================================
+ Coverage                       95.34%   98.34%   +3.00%     
==============================================================
  Files                             920      925       +5     
  Lines                          122856   123240     +384     
  Branches                        10681    10890     +209     
==============================================================
+ Hits                           117139   121206    +4067     
+ Misses                           5717     2034    -3683     
Flag Coverage Δ
aiguard 58.19% <79.22%> (+0.02%) ⬆️
aiguard-integration 57.07% <72.72%> (+0.02%) ⬆️
apm-bucket-0 58.46% <79.22%> (+0.02%) ⬆️
apm-bucket-1 64.55% <79.22%> (+0.01%) ⬆️
apm-bucket-2 63.60% <79.22%> (+0.01%) ⬆️
apm-bucket-3 60.90% <79.22%> (+0.02%) ⬆️
apm-capabilities-tracing 62.38% <100.00%> (+0.02%) ⬆️
apm-integrations-aerospike 57.59% <79.22%> (+0.02%) ⬆️
apm-integrations-confluentinc-kafka-javascript 62.49% <61.03%> (+0.01%) ⬆️
apm-integrations-couchbase 57.87% <79.22%> (+0.02%) ⬆️
apm-integrations-http 63.60% <79.22%> (+0.01%) ⬆️
apm-integrations-kafkajs 63.05% <61.03%> (+0.01%) ⬆️
apm-integrations-next 59.99% <61.03%> (+0.02%) ⬆️
apm-integrations-prisma 59.42% <61.03%> (+0.02%) ⬆️
appsec 73.90% <79.22%> (+0.02%) ⬆️
appsec-express_fastify_graphql 71.47% <61.03%> (+<0.01%) ⬆️
appsec-integration 51.90% <58.44%> (+0.01%) ⬆️
appsec-kafka_ldapjs_lodash 64.78% <61.03%> (+0.01%) ⬆️
appsec-mongodb-core_mongoose_mysql 68.50% <61.03%> (+<0.01%) ⬆️
appsec-next 58.16% <61.03%> (+0.02%) ⬆️
appsec-node-serialize_passport_postgres 68.19% <61.03%> (-0.03%) ⬇️
appsec-sourcing_stripe_template 66.50% <61.03%> (+<0.01%) ⬆️
debugger 65.84% <61.03%> (+<0.01%) ⬆️
instrumentations-bucket-0 52.32% <61.03%> (+0.03%) ⬆️
instrumentations-bucket-1 61.09% <61.03%> (+0.01%) ⬆️
instrumentations-bucket-10 62.94% <61.03%> (+0.01%) ⬆️
instrumentations-bucket-11 52.33% <61.03%> (+0.03%) ⬆️
instrumentations-bucket-12 52.78% <61.03%> (+0.03%) ⬆️
instrumentations-bucket-13 52.28% <61.03%> (+0.03%) ⬆️
instrumentations-bucket-2 54.25% <61.03%> (+0.03%) ⬆️
instrumentations-bucket-3 60.06% <61.03%> (+0.02%) ⬆️
instrumentations-bucket-4 52.91% <61.03%> (+0.03%) ⬆️
instrumentations-bucket-5 58.30% <61.03%> (+0.02%) ⬆️
instrumentations-bucket-6 61.64% <61.03%> (+0.01%) ⬆️
instrumentations-bucket-7 59.19% <61.03%> (+0.01%) ⬆️
instrumentations-bucket-8 60.46% <61.03%> (+0.01%) ⬆️
instrumentations-bucket-9 62.32% <61.03%> (+0.01%) ⬆️
instrumentations-instrumentation-couchbase 51.57% <61.03%> (+0.03%) ⬆️
instrumentations-integration-esbuild 34.03% <58.44%> (+0.04%) ⬆️
llmobs-ai_anthropic_bedrock 63.28% <61.03%> (+0.01%) ⬆️
llmobs-bucket-1 62.58% <79.22%> (+0.01%) ⬆️
llmobs-openai 63.25% <79.22%> (+0.01%) ⬆️
llmobs-sdk 65.46% <85.71%> (?)
llmobs-vertex-ai 59.94% <61.03%> (+0.02%) ⬆️
openfeature 54.67% <58.44%> (+0.01%) ⬆️
openfeature-unit 53.35% <61.03%> (+0.03%) ⬆️
platform-core_esbuild_instrumentations-misc 40.03% <61.03%> (?)
platform-integration 62.26% <75.32%> (+0.02%) ⬆️
platform-shimmer_unit-guardrails_webpack 39.33% <61.03%> (?)
plugins-bucket-0 57.73% <61.03%> (?)
plugins-bucket-1 55.15% <58.44%> (?)
plugins-bucket-11 63.06% <61.03%> (+0.01%) ⬆️
plugins-bucket-18 62.87% <79.22%> (?)
plugins-bucket-19 61.01% <79.22%> (+0.02%) ⬆️
plugins-bucket-20 62.86% <79.22%> (+0.01%) ⬆️
plugins-bucket-4 59.36% <61.03%> (?)
plugins-bullmq_cassandra_cookie 62.64% <79.22%> (?)
plugins-cookie-parser_crypto_dd-trace-api 57.50% <62.33%> (?)
plugins-fetch_fs_generic-pool 59.60% <79.22%> (?)
plugins-google-cloud-pubsub_grpc_handlebars 65.62% <79.22%> (?)
plugins-hapi_hono_ioredis 61.07% <79.22%> (+0.02%) ⬆️
plugins-jest_knex_langgraph 56.35% <61.03%> (+0.02%) ⬆️
plugins-ldapjs_light-my-request_limitd-client 59.31% <61.03%> (+0.02%) ⬆️
plugins-lodash_mariadb_memcached 58.82% <79.22%> (+0.02%) ⬆️
plugins-moleculer_mongodb_mongodb-core 62.80% <79.22%> (+0.01%) ⬆️
plugins-mongoose_multer_mysql 59.90% <79.22%> (+0.02%) ⬆️
plugins-mysql2_nats_node-serialize 61.68% <79.22%> (+0.02%) ⬆️
plugins-opensearch_passport-http_pino 60.46% <79.22%> (+0.02%) ⬆️
plugins-postgres_process_pug 59.10% <79.22%> (+0.02%) ⬆️
plugins-redis_router_sequelize 62.93% <61.03%> (+0.01%) ⬆️
plugins-test-and-upstream-rhea_undici_url 62.49% <79.22%> (-0.03%) ⬇️
plugins-valkey_vm_winston 58.76% <79.22%> (-0.02%) ⬇️
plugins-ws 60.43% <79.22%> (+0.02%) ⬆️
profiling 63.05% <75.32%> (+0.02%) ⬆️
serverless-aws-sdk-aws-sdk 55.70% <79.22%> (+0.02%) ⬆️
serverless-aws-sdk-bedrockruntime 55.42% <61.03%> (+0.02%) ⬆️
serverless-aws-sdk-client 57.15% <61.03%> (+0.02%) ⬆️
serverless-aws-sdk-dynamodb 56.34% <61.03%> (+0.01%) ⬆️
serverless-aws-sdk-eventbridge 49.87% <61.03%> (+0.03%) ⬆️
serverless-aws-sdk-kinesis 60.12% <61.03%> (+0.01%) ⬆️
serverless-aws-sdk-lambda 58.11% <61.03%> (+0.02%) ⬆️
serverless-aws-sdk-s3 56.28% <61.03%> (+0.02%) ⬆️
serverless-aws-sdk-serverless-peer-service 60.53% <61.03%> (+0.01%) ⬆️
serverless-aws-sdk-sns 60.93% <61.03%> (+0.01%) ⬆️
serverless-aws-sdk-sqs 61.36% <61.03%> (+0.01%) ⬆️
serverless-aws-sdk-stepfunctions 56.27% <61.03%> (+0.02%) ⬆️
serverless-aws-sdk-util 51.97% <61.03%> (+0.03%) ⬆️
serverless-bucket-0 55.19% <58.44%> (+0.02%) ⬆️
serverless-bucket-1 60.06% <79.22%> (+0.02%) ⬆️
test-optimization-cucumber 72.98% <61.03%> (+0.04%) ⬆️
test-optimization-cypress 66.38% <58.44%> (+<0.01%) ⬆️
test-optimization-jest 74.34% <75.32%> (+<0.01%) ⬆️
test-optimization-mocha 74.67% <61.03%> (-0.01%) ⬇️
test-optimization-playwright-playwright-atr 61.39% <58.44%> (+0.01%) ⬆️
test-optimization-playwright-playwright-efd 61.59% <58.44%> (+0.01%) ⬆️
test-optimization-playwright-playwright-final-status 61.55% <58.44%> (+0.01%) ⬆️
test-optimization-playwright-playwright-impacted-tests 61.27% <58.44%> (+0.01%) ⬆️
test-optimization-playwright-playwright-reporting 61.17% <58.44%> (+0.01%) ⬆️
test-optimization-playwright-playwright-test-management 62.12% <58.44%> (+0.01%) ⬆️
test-optimization-playwright-playwright-test-span 61.25% <58.44%> (+0.01%) ⬆️
test-optimization-selenium 60.68% <58.44%> (+0.01%) ⬆️
test-optimization-testopt 59.20% <72.72%> (+0.01%) ⬆️
test-optimization-vitest 71.29% <61.03%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bm1549 bm1549 closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos semver-patch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant