Skip to content

Allocate CtxWrap and OtelThreadCtxRecord together in memory - #402

Open
szegedi wants to merge 3 commits into
mainfrom
szegedi/less-indirection
Open

Allocate CtxWrap and OtelThreadCtxRecord together in memory#402
szegedi wants to merge 3 commits into
mainfrom
szegedi/less-indirection

Conversation

@szegedi

@szegedi szegedi commented Aug 24, 2026

Copy link
Copy Markdown

What does this PR do?:
Allocates CtxWrap and OtelThreadCtxRecord together in memory.

This allows us to store the pointer to OtelThreadCtxRecord in the JS wrapper's internal field 0 so the reader is never exposed to CtxWrap and can read the record with one pointer indirection fewer. Internally we can still find the CtxWrap by subtracting a fixed offset from the OtelThreadCtxRecord pointer.

Motivation:
By storing the OtelThreadCtxRecord directly in the JS object's internal field, an external reader can reach it directly from there instead of having to traverse CtxWrap too. CtxWrap becomes a purely internal implementation detail that the reader needs to know nothing about.

Jira: PROF-15807

@szegedi szegedi added the semver-minor Usually minor non-breaking improvements label Aug 24, 2026
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

Overall package size

Self size: 2.55 MB
Deduped: 3.26 MB
No deduping: 3.26 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | pprof-format | 2.3.1 | 504.33 kB | 504.33 kB | | source-map | 0.8.0 | 185.66 kB | 185.66 kB | | node-gyp-build | 4.8.4 | 13.86 kB | 13.86 kB |

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

@szegedi
szegedi force-pushed the szegedi/less-indirection branch from 0d27c43 to 6acf591 Compare August 24, 2026 12:55
@szegedi
szegedi marked this pull request as ready for review August 24, 2026 13:03
This allows us to store the pointer to OtelThreadCtxRecord in the JS
wrapper's internal field 0 so the reader is never exposed to CtxWrap
and can read the record with one pointer indirection fewer.
@szegedi
szegedi force-pushed the szegedi/less-indirection branch from 0a4c2a1 to 475868f Compare August 24, 2026 13:47
@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Aug 24, 2026

Copy link
Copy Markdown

Pipelines

⚠️ Warnings

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 6 Pipeline jobs failed

DataDog/apm-reliability/pprof-nodejs | benchmarks: [18]

View more details · View in GitLab

DataDog/apm-reliability/pprof-nodejs | benchmarks: [20]

View more details · View in GitLab

DataDog/apm-reliability/pprof-nodejs | benchmarks: [22]

View more details · View in GitLab

View all 6 failed jobs.

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: ad05c33 | Docs | View more details | Give us feedback!

@nsavoire

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 475868f31f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bindings/otel-thread-ctx.cc
Comment thread bindings/otel-thread-ctx.cc Outdated
// weak handle and so cancels the WeakCallback V8 would otherwise fire on
// the freed block.
CtxWrap::Destroy(self);
self = new_self;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: at first it looks like a dead store, a (short) comment to explain it is needed because of the previous defer would be helpful.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually just thinking about this this morning and I figured we don't even need to do this:

  • encoding_ guards against reentrancy, but all reentrancy can only happen within EncodeAttrs. Once that returns, we might as well flip encoding_ back to false and not deal with any of this further down the line.
  • Even if we keep the defer block, all reentrant calls would still be checking old self.encoding_ so new_self.encoding_ is never read here thus we might as well construct new_self with encoding_ = false and not reassign self.

Does this sound valid to you?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree on both points !

return;
}

// Doesn't fit. Reallocate with geometric growth with cap.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe extract this whole part into a separate function that returns the new CtxWrap

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thought about it and I wouldn't split the function like that. What might make more sense to me is to split the code into JS (V8 and Node) part (arg validation, reentrancy guard and few more concerns) that stays here in Append and then make a separate AppendEncoded that only deals with the memory mechanics of the native part (either in-place or growing.) E.g. Append would end with:

  if (!self->AppendEncoded(args.This(), appended)) {
    isolate->ThrowError("allocation failed");
  }

and we'd have:

bool AppendEncoded(Local<Object> holder, const std::vector<uint8_t>& appended);

Let me add this as a separate commit and then I'll let you review the result.

@szegedi
szegedi requested a review from nsavoire August 26, 2026 10:18

// Floor on the attrs_data capacity of a freshly allocated record. Sized so
// the total allocation is one 64-byte cache line — matching the OTEP-4947
// the record itself is one 64-byte cache line — matching the OTEP-4947

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would remove the cache line mention because nothing is done to ensure the record starts at a cache line boundary. Moreover with calloc alignment (16 bytes) and record offset (40 bytes), I don't think it's actually possible to make record starts on a cache line boundary.

// immediately after the object's own fields. Both record() and FromRecord()
// are defined in terms of it.
constexpr size_t RECORD_OFFSET = sizeof(CtxWrap);
static_assert(RECORD_OFFSET % alignof(OtelThreadCtxRecord) == 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: RECORD_OFFSET should also satisfy v8 aligned pointer requirementsfor SetAlignedPointerInInternalField (which it currently does since it's the same as OtelThreadCtxRecord: 2-byte alignment)

@nsavoire nsavoire left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left only small nits on comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver-minor Usually minor non-breaking improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants