Skip to content

repro: ws-stress fails on master via nyc-cache + dc-polyfill channel re-creation - #8297

Closed
bm1549 wants to merge 2 commits into
masterfrom
brian.marks/fix-ws-plugin-flake
Closed

bm1549 wants to merge 2 commits into
masterfrom
brian.marks/fix-ws-plugin-flake

Conversation

@bm1549

@bm1549 bm1549 commented May 6, 2026 •

Copy link
Copy Markdown
Collaborator

⚠️ Symptom reproducer, not a fix. Intentionally red. Do not merge.

What this PR contains

Master + the ws-stress workflow (10 runners × 25 iterations of npm run test:plugins:ci with PLUGINS=ws). Nothing else.

What it reproduces

ws-stress is deterministically red on master: 14 passing / 32 failing per iteration. Every failing test returns a Promise from agent.assertSomeTraces(...); every passing test uses a done() callback. Spans never reach the mock agent.

Locally on macOS arm64 / Node 18.20.8, the bug surfaces from iteration 2 (iteration 1 passes — the wall-clock cost of the fresh nyc transform must keep something pinned). On Linux x64 in CI, iteration 1 already fails.

Where the bug actually is

Node 18's diagnostics_channel.channel(name) returns different Channel objects across calls for the same name. Verified by tracing require('diagnostics_channel').channel('ws:send') (bypassing dc-polyfill entirely) inside packages/datadog-instrumentations/src/ws.js — across the test run, the native call returns object ids 1 → 2 → 3 → 5 → 6 for the same name. That's the underlying defect; everything downstream is fallout.

dc-polyfill tries to work around this in patch-garbage-collection-bug.js: on each channel(name) call it keeps the result alive via a phony subscriber and records the object in a WeakSet. The gap is that the WeakSet check is keyed by the returned Channel object — when Node returns a brand-new Channel for the same name, channels.has(ch) is false, dc-polyfill treats it as new, and you end up with multiple Channel instances for one name. The phony-subscriber strategy keeps each of them alive but doesn't unify them.

Why iter 1 passes and iter 2 fails

Both iterations see Node hand out multiple Channel objects for the same name. The difference is which one gets the dd-trace plugin's subscription. In iter 1, by chance, the plugin subscribes to the same Channel object that the publish-side closure (in the wrapped ws.prototype.send) has captured. In iter 2, Node hands out enough different objects that the publish-side and subscribe-side end up on different Channels and the publish goes nowhere as far as the plugin is concerned. assertSomeTraces waits 5 s and times out.

What changes between the two iterations is an indirect side effect of nyc's cache-hit path skipping work that the cache-miss path does — likely some retention pattern that pins the original Channel object. Confirmed by removing any one of ws.js, helpers/hook.js, helpers/register.js, or datadog-instrumentations/index.js from node_modules/.cache/nyc/ between iterations: iter 2 then passes. The cached files are byte-identical to fresh transforms (diff → 0 lines), so the cache-load path itself isn't producing wrong code; it's that running through the fresh-transform path leaves the system in a state where Node's broken registry doesn't bite.

Fix surfaces, in order of where the bug lives

  1. Node — make diagnostics_channel.channel(name) return a stable Channel object per name. Real fix; out of scope here.
  2. dc-polyfill — strengthen the workaround. Memoize by name instead of by Channel-object identity:
    const byName = new Map()
    dc.channel = function(name) {
      if (byName.has(name)) return byName.get(name)
      const ch = dc_channel.call(this, name)
      byName.set(name, ch)
      // ... existing phony-subscribe logic ...
      return ch
    }
    Empirically verified locally: applying this patch makes all 3 iterations of npm run test:plugins:ci pass. It papers over Node's bug rather than fixing it, but dc-polyfill is already the workaround layer.
  3. dd-trace instrumentation pattern — defer channel resolution. Don't capture producerCh.start etc. in module-level closures; resolve tracingChannel(name) inside each wrapped method (or behind a memoizing getter). Higher per-call cost; pushes the workaround into every integration.

What I tried but couldn't isolate

A minimal standalone reproducer outside dd-trace-js. dc.channel('foo') × 2 under nyc node, then with multiple module loads via delete require.cache, then with explicit --expose-gc and a sub/unsub cycle — channels stay identical in all three. Whatever scales the bug into visibility is something specific to dd-trace's test setup (proxyquire-heavy reload, plugin-manager destroy-and-recreate cycles, channel count). For now the stress workflow on this branch is the most compact reliable repro.

Reproducer recipe

CI: just push to this branch — ws-stress jobs will go red. Locally on Node 18.20.8:

unset OTEL_TRACES_EXPORTER OTEL_LOGS_EXPORTER OTEL_METRICS_EXPORTER  # set automatically by Claude Code / Cursor
_DD_IGNORE_ENGINES=true bun install --linker=hoisted --trust
PLUGINS=ws npm run test:plugins:ci   # iter 1 — passes
PLUGINS=ws npm run test:plugins:ci   # iter 2 — 14 pass / 32 fail

@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 May 6, 2026
@github-actions

github-actions Bot commented May 6, 2026 •

Copy link
Copy Markdown
Contributor

Overall package size

Self size: 5.8 MB
Deduped: 6.64 MB
No deduping: 6.64 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.0.1 | 82.56 kB | 817.39 kB | | dc-polyfill | 0.1.10 | 26.73 kB | 26.73 kB |

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

@datadog-prod-us1-5

This comment has been minimized.

@codecov

codecov Bot commented May 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.79%. Comparing base (66d4901) to head (c381672).
⚠️ Report is 15 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8297      +/-   ##
==========================================
+ Coverage   89.77%   89.79%   +0.02%     
==========================================
  Files         836      836              
  Lines       44487    44733     +246     
  Branches     8210     8278      +68     
==========================================
+ Hits        39937    40169     +232     
- Misses       4550     4564      +14     
Flag Coverage Δ
aiguard-integration-active 40.53% <ø> (-0.07%) ⬇️
aiguard-integration-latest 40.48% <ø> (-0.07%) ⬇️
aiguard-integration-maintenance 40.53% <ø> (-0.07%) ⬇️
aiguard-macos 35.23% <ø> (-0.14%) ⬇️
aiguard-ubuntu 35.33% <ø> (-0.14%) ⬇️
aiguard-windows 35.14% <ø> (-0.14%) ⬇️
apm-capabilities-tracing-macos 49.01% <ø> (-0.04%) ⬇️
apm-capabilities-tracing-ubuntu-active 49.04% <ø> (-0.04%) ⬇️
apm-capabilities-tracing-ubuntu-latest 49.01% <ø> (-0.04%) ⬇️
apm-capabilities-tracing-ubuntu-maintenance 49.22% <ø> (-0.03%) ⬇️
apm-capabilities-tracing-ubuntu-oldest 49.21% <ø> (-0.04%) ⬇️
apm-capabilities-tracing-windows 48.83% <ø> (-0.22%) ⬇️
apm-integrations-aerospike-18-gte.5.2.0 34.22% <ø> (-0.14%) ⬇️
apm-integrations-aerospike-20-gte.5.5.0 34.23% <ø> (-0.14%) ⬇️
apm-integrations-aerospike-22-gte.5.12.1 34.23% <ø> (-0.14%) ⬇️
apm-integrations-aerospike-22-gte.6.0.0 34.23% <ø> (-0.14%) ⬇️
apm-integrations-aerospike-eol- 34.14% <ø> (-0.14%) ⬇️
apm-integrations-child-process 35.25% <ø> (-0.14%) ⬇️
apm-integrations-confluentinc-kafka-javascript-18 41.09% <ø> (-0.15%) ⬇️
apm-integrations-confluentinc-kafka-javascript-20 41.11% <ø> (-0.15%) ⬇️
apm-integrations-confluentinc-kafka-javascript-22 41.11% <ø> (-0.26%) ⬇️
apm-integrations-confluentinc-kafka-javascript-24 41.04% <ø> (-0.15%) ⬇️
apm-integrations-couchbase-18 34.49% <ø> (-0.06%) ⬇️
apm-integrations-couchbase-eol 34.43% <ø> (-0.01%) ⬇️
apm-integrations-dns 34.13% <ø> (-0.14%) ⬇️
apm-integrations-elasticsearch 34.88% <ø> (-0.13%) ⬇️
apm-integrations-http-latest 42.08% <ø> (-0.13%) ⬇️
apm-integrations-http-maintenance 42.14% <ø> (-0.13%) ⬇️
apm-integrations-http-oldest 42.14% <ø> (-0.13%) ⬇️
apm-integrations-http2 39.45% <ø> (-0.26%) ⬇️
apm-integrations-kafkajs-latest 40.98% <ø> (-0.15%) ⬇️
apm-integrations-kafkajs-oldest 41.03% <ø> (-0.15%) ⬇️
apm-integrations-net 34.82% <ø> (-0.14%) ⬇️
apm-integrations-next-11.1.4 29.28% <ø> (-0.12%) ⬇️
apm-integrations-next-13.2.0 30.72% <ø> (-0.13%) ⬇️
apm-integrations-next-gte.10.2.0.and.lt.11 22.99% <ø> (ø)
apm-integrations-next-gte.11.0.0.and.lt.13 30.73% <ø> (-0.13%) ⬇️
apm-integrations-next-gte.13.0.0.and.lt.14 30.97% <ø> (-0.13%) ⬇️
apm-integrations-next-gte.14.0.0.and.lte.14.2.6 30.79% <ø> (-0.13%) ⬇️
apm-integrations-next-gte.14.2.7.and.lt.15 30.81% <ø> (-0.11%) ⬇️
apm-integrations-next-gte.15.0.0 30.86% <ø> (-0.13%) ⬇️
apm-integrations-oracledb 34.52% <ø> (-0.14%) ⬇️
apm-integrations-prisma-18-gte.6.16.0.and.lt.7.0.0 34.86% <ø> (-0.13%) ⬇️
apm-integrations-prisma-latest-all 35.18% <ø> (-0.13%) ⬇️
apm-integrations-restify 36.32% <ø> (-0.21%) ⬇️
apm-integrations-sharedb 33.81% <ø> (-0.14%) ⬇️
apm-integrations-tedious 34.32% <ø> (-0.14%) ⬇️
appsec-express 51.93% <ø> (-0.11%) ⬇️
appsec-fastify 48.59% <ø> (-0.12%) ⬇️
appsec-graphql 48.90% <ø> (-0.12%) ⬇️
appsec-integration-active 37.00% <ø> (-0.27%) ⬇️
appsec-integration-latest 36.97% <ø> (-0.27%) ⬇️
appsec-integration-maintenance 36.99% <ø> (-0.27%) ⬇️
appsec-integration-oldest 36.98% <ø> (-0.27%) ⬇️
appsec-kafka 41.44% <ø> (-0.13%) ⬇️
appsec-ldapjs 40.65% <ø> (-0.13%) ⬇️
appsec-lodash 40.76% <ø> (-0.13%) ⬇️
appsec-macos 55.75% <ø> (-0.13%) ⬇️
appsec-mongodb-core 45.02% <ø> (-0.12%) ⬇️
appsec-mongoose 45.88% <ø> (-0.13%) ⬇️
appsec-mysql 47.92% <ø> (-0.16%) ⬇️
appsec-next-latest-11.1.4 29.40% <ø> (-0.12%) ⬇️
appsec-next-latest-13.2.0 30.86% <ø> (-0.13%) ⬇️
appsec-next-latest-gte.10.2.0.and.lt.11 28.66% <ø> (ø)
appsec-next-latest-gte.11.0.0.and.lt.13 30.84% <ø> (-0.13%) ⬇️
appsec-next-latest-gte.13.0.0.and.lt.14 31.09% <ø> (-0.14%) ⬇️
appsec-next-latest-gte.14.0.0.and.lte.14.2.6 30.93% <ø> (-0.13%) ⬇️
appsec-next-latest-gte.14.2.7.and.lt.15 30.93% <ø> (-0.13%) ⬇️
appsec-next-latest-gte.15.0.0 30.93% <ø> (-0.13%) ⬇️
appsec-next-oldest-11.1.4 29.41% <ø> (-0.12%) ⬇️
appsec-next-oldest-13.2.0 31.13% <ø> (-0.14%) ⬇️
appsec-next-oldest-gte.10.2.0.and.lt.11 28.78% <ø> (ø)
appsec-next-oldest-gte.11.0.0.and.lt.13 30.86% <ø> (-0.13%) ⬇️
appsec-next-oldest-gte.13.0.0.and.lt.14 31.33% <ø> (-0.13%) ⬇️
appsec-next-oldest-gte.14.0.0.and.lte.14.2.6 31.17% <ø> (-0.13%) ⬇️
appsec-next-oldest-gte.14.2.7.and.lt.15 31.17% <ø> (-0.13%) ⬇️
appsec-next-oldest-gte.15.0.0 31.17% <ø> (-0.13%) ⬇️
appsec-node-serialize 39.97% <ø> (-0.13%) ⬇️
appsec-passport 43.63% <ø> (-0.13%) ⬇️
appsec-postgres 47.51% <ø> (-0.12%) ⬇️
appsec-sourcing 39.31% <ø> (-0.12%) ⬇️
appsec-stripe 41.41% <ø> (-0.13%) ⬇️
appsec-template 40.13% <ø> (-0.13%) ⬇️
appsec-ubuntu 55.83% <ø> (-0.13%) ⬇️
appsec-windows 55.67% <ø> (-0.13%) ⬇️
debugger-ubuntu-active 43.17% <ø> (-0.12%) ⬇️
debugger-ubuntu-latest 43.12% <ø> (-0.07%) ⬇️
debugger-ubuntu-maintenance 43.18% <ø> (-0.07%) ⬇️
debugger-ubuntu-oldest 43.62% <ø> (-0.12%) ⬇️
instrumentations-instrumentation-bluebird 29.25% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-body-parser 36.86% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-child_process 34.74% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-cookie-parser 31.09% <ø> (-0.13%) ⬇️
instrumentations-instrumentation-express 31.31% <ø> (-0.13%) ⬇️
instrumentations-instrumentation-express-mongo-sanitize 31.21% <ø> (-0.13%) ⬇️
instrumentations-instrumentation-express-session 36.55% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-fs 28.94% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-generic-pool 29.86% <ø> (-0.03%) ⬇️
instrumentations-instrumentation-http 36.22% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-knex 29.22% <ø> (-0.13%) ⬇️
instrumentations-instrumentation-light-my-request 36.48% <ø> (-0.13%) ⬇️
instrumentations-instrumentation-mongoose 30.34% <ø> (-0.04%) ⬇️
instrumentations-instrumentation-multer 36.65% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-mysql2 34.72% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-passport 40.27% <ø> (-0.13%) ⬇️
instrumentations-instrumentation-passport-http 40.05% <ø> (-0.13%) ⬇️
instrumentations-instrumentation-passport-local 40.53% <ø> (-0.13%) ⬇️
instrumentations-instrumentation-pg 34.27% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-promise 29.19% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-promise-js 29.19% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-q 29.22% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-url 29.19% <ø> (-0.14%) ⬇️
instrumentations-instrumentation-when 29.21% <ø> (-0.14%) ⬇️
instrumentations-integration-esbuild-active 19.35% <ø> (-0.17%) ⬇️
instrumentations-integration-esbuild-latest 19.33% <ø> (-0.17%) ⬇️
instrumentations-integration-esbuild-maintenance 19.35% <ø> (-0.17%) ⬇️
instrumentations-integration-esbuild-oldest 19.34% <ø> (-0.17%) ⬇️
llmobs-ai 37.69% <ø> (-0.14%) ⬇️
llmobs-anthropic 37.75% <ø> (-0.14%) ⬇️
llmobs-bedrock 36.45% <ø> (-0.13%) ⬇️
llmobs-google-genai 36.77% <ø> (-0.13%) ⬇️
llmobs-langchain 36.49% <ø> (-0.12%) ⬇️
llmobs-openai 40.52% <ø> (-0.13%) ⬇️
llmobs-sdk-active 45.33% <ø> (-0.16%) ⬇️
llmobs-sdk-latest 45.26% <ø> (-0.16%) ⬇️
llmobs-sdk-maintenance 45.33% <ø> (-0.16%) ⬇️
llmobs-sdk-oldest 45.31% <ø> (-0.16%) ⬇️
llmobs-vertex-ai 36.92% <ø> (-0.13%) ⬇️
openfeature-macos 37.90% <ø> (-0.05%) ⬇️
openfeature-ubuntu 37.98% <ø> (-0.06%) ⬇️
openfeature-unit-active 48.51% <ø> (+0.02%) ⬆️
openfeature-unit-latest 48.35% <ø> (+0.02%) ⬆️
openfeature-unit-maintenance 48.51% <ø> (+0.02%) ⬆️
openfeature-unit-oldest 48.51% <ø> (+0.02%) ⬆️
openfeature-windows 37.78% <ø> (+<0.01%) ⬆️
platform-core 37.27% <ø> (ø)
platform-esbuild 40.61% <ø> (ø)
platform-instrumentations-misc 30.69% <ø> (-0.05%) ⬇️
platform-integration-active 46.92% <ø> (-0.08%) ⬇️
platform-integration-latest 46.88% <ø> (-0.07%) ⬇️
platform-integration-maintenance 46.93% <ø> (-0.07%) ⬇️
platform-integration-oldest 47.10% <ø> (-0.08%) ⬇️
platform-shimmer 42.29% <ø> (ø)
platform-unit-guardrails 36.12% <ø> (ø)
platform-webpack 20.47% <ø> (-0.16%) ⬇️
plugins-azure-durable-functions 36.91% <ø> (-0.07%) ⬇️
plugins-azure-event-hubs 34.75% <ø> (-0.07%) ⬇️
plugins-azure-service-bus 35.22% <ø> (-0.07%) ⬇️
plugins-bullmq 40.01% <ø> (-0.15%) ⬇️
plugins-cassandra 34.66% <ø> (-0.01%) ⬇️
plugins-cookie 26.65% <ø> (ø)
plugins-cookie-parser 26.46% <ø> (ø)
plugins-crypto 27.69% <ø> (ø)
plugins-dd-trace-api 34.67% <ø> (-0.14%) ⬇️
plugins-express-mongo-sanitize 26.59% <ø> (ø)
plugins-express-session 26.42% <ø> (ø)
plugins-fastify 38.63% <ø> (-0.14%) ⬇️
plugins-fetch 34.96% <ø> (-0.13%) ⬇️
plugins-fs 35.02% <ø> (-0.14%) ⬇️
plugins-generic-pool 25.58% <ø> (ø)
plugins-google-cloud-pubsub 42.34% <ø> (-0.14%) ⬇️
plugins-grpc 37.40% <ø> (-0.14%) ⬇️
plugins-handlebars 26.63% <ø> (ø)
plugins-hapi 36.52% <ø> (-0.14%) ⬇️
plugins-hono 36.81% <ø> (-0.14%) ⬇️
plugins-ioredis 35.10% <ø> (-0.14%) ⬇️
plugins-knex 26.31% <ø> (ø)
plugins-langgraph 34.38% <ø> (-0.14%) ⬇️
plugins-ldapjs 24.20% <ø> (ø)
plugins-light-my-request 26.06% <ø> (ø)
plugins-limitd-client 29.49% <ø> (-0.20%) ⬇️
plugins-lodash 25.65% <ø> (ø)
plugins-mariadb 35.95% <ø> (-0.14%) ⬇️
plugins-memcached 34.76% <ø> (-0.14%) ⬇️
plugins-microgateway-core 35.65% <ø> (-0.14%) ⬇️
plugins-modelcontextprotocol-sdk 33.66% <ø> (-0.14%) ⬇️
plugins-moleculer 37.48% <ø> (-0.14%) ⬇️
plugins-mongodb 35.90% <ø> (-0.14%) ⬇️
plugins-mongodb-core 35.52% <ø> (-0.14%) ⬇️
plugins-mongoose 35.40% <ø> (-0.21%) ⬇️
plugins-multer 26.42% <ø> (ø)
plugins-mysql 35.48% <ø> (-0.14%) ⬇️
plugins-mysql2 35.77% <ø> (-0.14%) ⬇️
plugins-node-serialize 26.69% <ø> (ø)
plugins-opensearch 34.41% <ø> (-0.14%) ⬇️
plugins-passport-http 26.47% <ø> (ø)
plugins-pino 31.19% <ø> (-0.14%) ⬇️
plugins-postgres 33.71% <ø> (-0.13%) ⬇️
plugins-process 27.69% <ø> (ø)
plugins-pug 26.65% <ø> (ø)
plugins-redis 35.16% <ø> (-0.14%) ⬇️
plugins-router 39.10% <ø> (-0.01%) ⬇️
plugins-sequelize 25.36% <ø> (ø)
plugins-test-and-upstream-amqp10 35.00% <ø> (-0.14%) ⬇️
plugins-test-and-upstream-amqplib 40.23% <ø> (-0.15%) ⬇️
plugins-test-and-upstream-apollo 35.87% <ø> (-0.13%) ⬇️
plugins-test-and-upstream-avsc 34.34% <ø> (-0.14%) ⬇️
plugins-test-and-upstream-bunyan 30.57% <ø> (-0.14%) ⬇️
plugins-test-and-upstream-connect 37.19% <ø> (-0.14%) ⬇️
plugins-test-and-upstream-graphql 36.78% <ø> (-0.14%) ⬇️
plugins-test-and-upstream-koa 36.78% <ø> (-0.14%) ⬇️
plugins-test-and-upstream-protobufjs 34.55% <ø> (-0.14%) ⬇️
plugins-test-and-upstream-rhea 40.24% <ø> (-0.15%) ⬇️
plugins-undici 35.56% <ø> (-0.25%) ⬇️
plugins-url 27.69% <ø> (ø)
plugins-valkey 34.77% <ø> (-0.14%) ⬇️
plugins-vm 27.69% <ø> (ø)
plugins-winston 31.08% <ø> (-0.13%) ⬇️
plugins-ws 38.19% <ø> (-0.28%) ⬇️
profiling-macos 42.72% <ø> (-0.18%) ⬇️
profiling-ubuntu 43.08% <ø> (-0.18%) ⬇️
profiling-windows 39.98% <ø> (-0.10%) ⬇️
serverless-aws-sdk-latest-aws-sdk 34.74% <ø> (-0.12%) ⬇️
serverless-aws-sdk-latest-bedrockruntime 33.27% <ø> (-0.13%) ⬇️
serverless-aws-sdk-latest-client 22.38% <ø> (ø)
serverless-aws-sdk-latest-dynamodb 35.70% <ø> (-0.13%) ⬇️
serverless-aws-sdk-latest-eventbridge 28.77% <ø> (-0.12%) ⬇️
serverless-aws-sdk-latest-kinesis 38.61% <ø> (-0.13%) ⬇️
serverless-aws-sdk-latest-lambda 35.89% <ø> (-0.13%) ⬇️
serverless-aws-sdk-latest-s3 33.82% <ø> (-0.13%) ⬇️
serverless-aws-sdk-latest-serverless-peer-service 39.99% <ø> (-0.16%) ⬇️
serverless-aws-sdk-latest-sns 39.87% <ø> (-0.14%) ⬇️
serverless-aws-sdk-latest-sqs 39.00% <ø> (-0.13%) ⬇️
serverless-aws-sdk-latest-stepfunctions 34.47% <ø> (-0.12%) ⬇️
serverless-aws-sdk-latest-util 47.71% <ø> (ø)
serverless-aws-sdk-oldest-aws-sdk 34.81% <ø> (-0.12%) ⬇️
serverless-aws-sdk-oldest-bedrockruntime 33.31% <ø> (-0.13%) ⬇️
serverless-aws-sdk-oldest-client 22.75% <ø> (ø)
serverless-aws-sdk-oldest-dynamodb 35.74% <ø> (-0.13%) ⬇️
serverless-aws-sdk-oldest-eventbridge 28.81% <ø> (-0.12%) ⬇️
serverless-aws-sdk-oldest-kinesis 38.74% <ø> (-0.13%) ⬇️
serverless-aws-sdk-oldest-lambda 35.94% <ø> (-0.13%) ⬇️
serverless-aws-sdk-oldest-s3 33.88% <ø> (-0.13%) ⬇️
serverless-aws-sdk-oldest-serverless-peer-service 40.06% <ø> (-0.16%) ⬇️
serverless-aws-sdk-oldest-sns 40.04% <ø> (-0.02%) ⬇️
serverless-aws-sdk-oldest-sqs 39.05% <ø> (-0.13%) ⬇️
serverless-aws-sdk-oldest-stepfunctions 34.51% <ø> (-0.12%) ⬇️
serverless-aws-sdk-oldest-util 48.03% <ø> (ø)
serverless-azure-functions-eventhubs 38.40% <ø> (-0.07%) ⬇️
serverless-azure-functions-servicebus 38.46% <ø> (-0.07%) ⬇️
serverless-lambda 32.76% <ø> (-0.13%) ⬇️
test-optimization-cucumber-latest-7.0.0 50.42% <ø> (+0.26%) ⬆️
test-optimization-cucumber-latest-latest 53.05% <ø> (+0.18%) ⬆️
test-optimization-cucumber-oldest-7.0.0 50.35% <ø> (+0.17%) ⬆️
test-optimization-cypress-latest-12.0.0-commonJS 48.25% <ø> (+0.07%) ⬆️
test-optimization-cypress-latest-12.0.0-esm 47.49% <ø> (-0.75%) ⬇️
test-optimization-cypress-latest-14.5.4-commonJS 48.10% <ø> (+0.04%) ⬆️
test-optimization-cypress-latest-14.5.4-esm 48.13% <ø> (+0.07%) ⬆️
test-optimization-cypress-latest-latest-commonJS 48.60% <ø> (+0.06%) ⬆️
test-optimization-cypress-latest-latest-esm 48.63% <ø> (+0.06%) ⬆️
test-optimization-cypress-oldest-12.0.0-commonJS 48.25% <ø> (+0.03%) ⬆️
test-optimization-cypress-oldest-12.0.0-esm 48.32% <ø> (+0.07%) ⬆️
test-optimization-cypress-oldest-14.5.4-commonJS 48.14% <ø> (+0.07%) ⬆️
test-optimization-cypress-oldest-14.5.4-esm 48.17% <ø> (+0.07%) ⬆️
test-optimization-jest-latest-latest 54.47% <ø> (+0.15%) ⬆️
test-optimization-jest-latest-oldest 53.64% <ø> (+0.18%) ⬆️
test-optimization-jest-oldest-latest 54.47% <ø> (+0.15%) ⬆️
test-optimization-jest-oldest-oldest 53.57% <ø> (+0.15%) ⬆️
test-optimization-mocha-latest-latest 53.33% <ø> (+0.14%) ⬆️
test-optimization-mocha-latest-oldest 50.94% <ø> (+0.13%) ⬆️
test-optimization-mocha-oldest-latest 53.39% <ø> (+0.14%) ⬆️
test-optimization-mocha-oldest-oldest 50.88% <ø> (+0.13%) ⬆️
test-optimization-playwright-latest-latest-playwright-active-test-span 44.26% <ø> (+0.85%) ⬆️
test-optimization-playwright-latest-latest-playwright-atr 43.03% <ø> (+0.69%) ⬆️
test-optimization-playwright-latest-latest-playwright-efd 43.40% <ø> (+0.78%) ⬆️
test-optimization-playwright-latest-latest-playwright-final-status 43.49% <ø> (+0.69%) ⬆️
test-optimization-playwright-latest-latest-playwright-impacted-tests 42.96% <ø> (+0.62%) ⬆️
test-optimization-playwright-latest-latest-playwright-reporting 42.45% <ø> (+0.06%) ⬆️
test-optimization-playwright-latest-latest-playwright-test-management 44.63% <ø> (+0.31%) ⬆️
test-optimization-playwright-latest-oldest-playwright-active-test-span 44.30% <ø> (+0.86%) ⬆️
test-optimization-playwright-latest-oldest-playwright-atr 43.21% <ø> (+0.70%) ⬆️
test-optimization-playwright-latest-oldest-playwright-efd 43.43% <ø> (+0.80%) ⬆️
test-optimization-playwright-latest-oldest-playwright-final-status 43.53% <ø> (+0.70%) ⬆️
test-optimization-playwright-latest-oldest-playwright-impacted-tests 42.98% <ø> (+0.61%) ⬆️
test-optimization-playwright-latest-oldest-playwright-reporting 42.49% <ø> (+0.06%) ⬆️
test-optimization-playwright-latest-oldest-playwright-test-management 44.68% <ø> (+0.32%) ⬆️
test-optimization-playwright-oldest-latest-playwright-active-test-span 44.27% <ø> (+0.83%) ⬆️
test-optimization-playwright-oldest-latest-playwright-atr 43.06% <ø> (+0.69%) ⬆️
test-optimization-playwright-oldest-latest-playwright-efd 43.41% <ø> (+0.78%) ⬆️
test-optimization-playwright-oldest-latest-playwright-final-status 43.51% <ø> (+0.67%) ⬆️
test-optimization-playwright-oldest-latest-playwright-impacted-tests 42.99% <ø> (+0.60%) ⬆️
test-optimization-playwright-oldest-latest-playwright-reporting 42.46% <ø> (+0.06%) ⬆️
test-optimization-playwright-oldest-latest-playwright-test-management 44.64% <ø> (+0.31%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-active-test-span 44.34% <ø> (+0.86%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-atr 43.25% <ø> (+0.70%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-efd 43.44% <ø> (+0.80%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-final-status 43.54% <ø> (+0.68%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-impacted-tests 43.02% <ø> (+0.61%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-reporting 42.50% <ø> (+0.06%) ⬆️
test-optimization-playwright-oldest-oldest-playwright-test-management 44.70% <ø> (+0.31%) ⬆️
test-optimization-selenium-latest 45.56% <ø> (+0.13%) ⬆️
test-optimization-selenium-oldest 45.05% <ø> (+0.14%) ⬆️
test-optimization-testopt-active 46.94% <ø> (+0.07%) ⬆️
test-optimization-testopt-latest 46.90% <ø> (+0.07%) ⬆️
test-optimization-testopt-maintenance 46.94% <ø> (+0.07%) ⬆️
test-optimization-testopt-oldest 47.81% <ø> (+0.07%) ⬆️
test-optimization-vitest-latest 50.95% <ø> (+0.11%) ⬆️
test-optimization-vitest-oldest 47.68% <ø> (+0.35%) ⬆️

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

☔ View full report in Codecov by Sentry.
📢 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.

@pr-commenter

pr-commenter Bot commented May 7, 2026 •

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-05-08 13:52:02

Comparing candidate commit c381672 in PR branch brian.marks/fix-ws-plugin-flake with baseline commit 66d4901 in branch master.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 1735 metrics, 109 unstable metrics.

10 parallel runners × 25 iterations to confirm the ws plugin fix
holds under repeated test:plugins:ci execution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bm1549
bm1549 force-pushed the brian.marks/fix-ws-plugin-flake branch from 7f363ba to 5139166 Compare May 8, 2026 00:06
@bm1549 bm1549 changed the title fix(test/plugins): close WebSocket clients in afterEach to prevent cascading ws test timeouts repro: ws-stress fails with nyc-cache + dc-polyfill channel-GC interaction May 8, 2026
@bm1549 bm1549 changed the title repro: ws-stress fails with nyc-cache + dc-polyfill channel-GC interaction repro: ws-stress fails on master via nyc-cache + dc-polyfill channel re-creation May 8, 2026
Adds a tiny pre-test step that patches node_modules/dc-polyfill to
memoize dc.channel(name) results by name. Locally this makes the
ws-stress repro pass on all 3 iterations.

Pushing to verify the same fix works in CI. NOT a permanent fix — the
proper home for this workaround is dc-polyfill itself; the underlying
bug is in Node 18's diagnostics_channel returning different Channel
objects across calls for the same name.
bm1549 added a commit to DataDog/dc-polyfill that referenced this pull request May 8, 2026
)

* fix: memoize channel(name) to make Channel identity stable per name

The phony AVOID_GARBAGE_COLLECTION subscriber installed by this patch
keeps a created Channel alive, but Node's underlying channel(name) can
still return a brand-new Channel object for the same name on a
subsequent call (observed on Node 18 under certain workloads — e.g.
heavy proxyquire reload + plugin teardown/recreate cycles). Because the
existing anti-GC tracking is a WeakSet keyed by Channel-object
identity, each new object is treated as new and gets its own phony
subscriber. The two objects are never unified.

The visible failure mode for callers: code that captures a Channel in
a module-level closure (a common pattern for instrumentation
publishers) ends up publishing to a different Channel than later
subscribers attach to. Publishes go nowhere; subscribers see nothing.
hasSubscribers reports false (only the phony is on the publish-side
Channel) and traceSync/traceCallback fast-path-bypass the publish.

This fix memoizes dc.channel(name) by name and short-circuits before
delegating to channel() on subsequent calls. Channel identity is now
stable per name regardless of what the underlying channel() returns
— the contract callers actually depend on.

Empirically verified end-to-end against dd-trace-js's ws plugin
stress workflow (10 runners × 25 iterations of plugin tests on
Node 18.20.8, 250 runs total): 0/10 runners pass without this patch,
10/10 pass with it. dd-trace-js PR for context: DataDog/dd-trace-js#8297.

All existing dc-polyfill tests pass (26/26 suites).

* test: regression test for memoized channel(name) identity

Adds a unit test that injects a mock unpatched module whose channel(name)
returns a different Channel object on every call, simulating the Node 18
bug. Without the memoization in patch-garbage-collection-bug.js, this
test fails 6/8 assertions (channel identity diverges, subscribers attach
to a different Channel than later publishers, the underlying channel()
is re-invoked on every memoizable lookup). With the patch, all 8 pass.

This pins the contract the patch is meant to enforce — dc.channel(name)
returns a stable Channel object per name regardless of what the
underlying registry hands back — without requiring a real Node 18 bug
to be triggered to exercise the patch.

* Apply suggestion from @bm1549
@BridgeAR BridgeAR closed this May 9, 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.

3 participants