Skip to content

Ruby skip native resume#80

Draft
Gandem wants to merge 19 commits into
datadogfrom
ruby-skip-native-resume
Draft

Ruby skip native resume#80
Gandem wants to merge 19 commits into
datadogfrom
ruby-skip-native-resume

Conversation

@Gandem

@Gandem Gandem commented May 20, 2026

Copy link
Copy Markdown
Member

No description provided.

nsavoire and others added 15 commits April 28, 2026 14:05
* Avoid using the GITHUB_TOKEN for pr creation

* fixing repo reference
)

Upstream defines some jobs that are skipped when run in forks (codeQL analysis).
Force-pushing the datadog branch makes old commit hashes unreachable,
breaking go.mod replace directives that reference them by pseudo-version.
- Compare `mergeable` against the MERGEABLE enum string instead of the
  boolean `false`, so CONFLICTING/UNKNOWN PRs are correctly rejected.
- Guard workflow dispatch to the `rebase-on-upstream` branch so an
  approved PR from an unrelated branch cannot force-push `datadog`.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…open-telemetry#1400) (#79)

Co-authored-by: Christos Kalkanis <christos.kalkanis@elastic.co>
Replace the `systemProcess.fileToMapping` map (populated as a side
effect of IterateMappings, then consulted by OpenELF) with an
explicit `OpenELFMapping(*RawMapping)` method on the Process
interface. The mapping is now passed directly, removing the temporal
coupling between the two methods and the path-keyed lookup.

In processmanager.newFrameMapping, a small `mappingELFOpener` adapter
routes opens of the bound mapping's path to `OpenELFMapping` while
forwarding everything else (e.g. .gnu_debuglink targets) to `OpenELF`.

This also fixes some issues with `fileToMapping`:
- The first iteration after process creation missed the map_files
  route because `sp.fileToMapping` was assigned only at the end of
  `IterateMappings` and subsequent iterations could use a stale
  RawMapping (from the previous iteration).
- Avoid path-keyed lookup that could pick the wrong file in some
  very rare cases (open-telemetry#812).
When the Ruby unwinder encounters a cfunc (C-implemented Ruby method),
it normally transitions back to the native unwinder to unwind through
the C code, then re-enters the Ruby unwinder when it detects the Ruby
interpreter loop. Each such round-trip costs 2-3 tail calls out of a
budget of 29, so a Ruby stack with 10+ cfuncs can easily truncate.

This adds a skip_native_resume flag to RubyProcInfo that, when set,
pushes cfunc frames inline without transitioning to the native unwinder.
This trades native frames within cfuncs (e.g., the C call chain inside
Array#sort) for complete Ruby-level traces without truncation.

The flag is controlled by OTEL_EBPF_RUBY_SKIP_NATIVE_RESUME (default:
false). Set to 'true' to enable.
- Correct inline comment that incorrectly stated 'Default to skipping'
  when the actual default is false (requires explicit opt-in)
- Add note that skipNativeResume() is evaluated per-process at attach time
When skip_native_resume is true, the end-of-stack check should stop
instead of resuming native unwinding. The native unwinder could
encounter the JIT anonymous mapping and re-enter the Ruby unwinder,
causing incorrect frames.

Check skip_native_resume at end-of-stack to prevent resuming native
unwinding when the flag is set.
Regenerates tracer.ebpf.{amd64,arm64} against the datadog branch HEAD
after cherry-picking PR open-telemetry#1335 (ruby: add skip_native_resume).
@datadog-prod-us1-3

This comment has been minimized.

Gandem added 4 commits May 20, 2026 16:35
Collapse the skip_native_resume ternary onto one line to satisfy
clang-format-17, and regenerate tracer.ebpf.{amd64,arm64}.
Per Benoit Daloze: defaulting to true avoids having to plumb the env var
through DOE deployments. Users can still opt out via
OTEL_EBPF_RUBY_SKIP_NATIVE_RESUME=false.
In walk_ruby_stack, when skip_native_resume is set and we reach the end
of the Ruby VM stack, the inner for-loop set *next_unwinder to
PROG_UNWIND_STOP but only `goto save_state` on PROG_UNWIND_NATIVE. The
loop continued with stack_ptr unchanged, re-reading the same cfp on every
iteration, and the natural for-loop exit then overwrote *next_unwinder
with PROG_UNWIND_RUBY — causing the Ruby unwinder to tail-call itself
repeatedly until the trace buffer overflowed with stack_length_exceeded.

Reproduced locally with OTEL_EBPF_RUBY_SKIP_NATIVE_RESUME=true against
the arm64 ruby-4.0.1-loop coredump: before the fix, the unwinder
returned ~150 duplicated "<main>+0 in /tmp/loop.rb:30" frames and aborted.
After the fix, every Ruby (ISEQ + cfunc) frame is pushed exactly once,
matching the goal of avoiding the Ruby<->native tail-call round-trips
without truncating the stack.

The original (skip_native_resume=false) path is unchanged: all 17 Ruby
arm64 coredump fixtures still pass.
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

This PR was marked stale due to lack of activity. It will be closed in 14 days.

@github-actions github-actions Bot added the Stale label Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants