Skip to content

Prevent ClassLatch from latching wrappers with defaults methods - #12823

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 3 commits into
masterfrom
bdu/preserve-class-latch-default-methods
Oct 14, 2026
Merged

gh-worker-dd-mergequeue-cf854d[bot] merged 3 commits into
masterfrom
bdu/preserve-class-latch-default-methods

Conversation

@bric3

@bric3 bric3 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What Does This Do

Prevents an AbstractMethodError thrown by a wrapper's delegate from latching the wrapper class when interface "evolution" exposes an abstract declaration alongside an inherited default implementation.

Motivation

On Java 8, these delegate failures have no message. The fallback added in #12799 inspected only abstract public methods, which could treat the wrapper as missing the method and disable calls for every healthy instance of that class.

Granted, this case seems a tad exotic, but its fix looks easy enough to handle that case.

Suppose the following, in version A, compiled together.

interface Api {}
interface Forwarding {
  Supplier<String> delegate();

  default String read() {
    return delegate().get();
  }
}
final class Wrapper implements Api, Forwarding {
  private final Supplier<String> delegate;

  Wrapper(Supplier<String> delegate) {
    this.delegate = delegate;
  }

  @Override
  public Supplier<String> delegate() {
    return delegate;
  }
}

Then there's version B of Api with a new method that has the same signature as Forwarding's default method, while the existing Wrapper.class remains as is:

interface Api {
  String read();
}

When invoking wrapper.read() the JVM would still call Forwarding.read(). However, Wrapper.class.getMethods() now exposes both:

  • public abstract String Api.read()
  • public default String Forwarding.read()

With the following latch

ClassLatch<Api, String, RuntimeException> latch =
    new ClassLatch<Api, String, RuntimeException>() {
      @Override
      protected String apply(Api target) {
        return handleAbstractMethod(target, "read", Api::read);
      }
    };

Api healthy = new Wrapper(() -> "ok");
Api failing = new Wrapper(() -> {
  throw new AbstractMethodError(); // No message, as on Java 8
});

latch.tryApply(healthy); // "ok"
latch.tryApply(failing); // null; incorrectly latches Wrapper.class
latch.tryApply(healthy); // null; expected "ok"

Upon error the cache sees the abstract Api.read() method and concludes that Wrapper lacks a read() method. It overlooked the working Forwarding.read(), so this permanently disables the operation for every Wrapper.
The explicit throw keeps this example small; the reproduction I ran obtained the message-less error from the JVM. This requires keeping the old wrapper binary: recompiling it against the upgraded Api would reject the conflicting declarations.

Additional Notes

The cache remains a String[]. The fix filters the array in place instead of building a HashSet, so it preserves the existing allocation shape. It adds a second bounded scan only when the cache is built after the first message-less failure for a class. A temporary Java 8 JMH comparison found the array scan faster than the HashSet variant on method-heavy types. Healthy and cached paths are unchanged.

Contributor Checklist

@bric3 bric3 added type: bug fix Bug fix comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM labels Oct 12, 2026
@datadog-prod-us1-4

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

🟡 Java Benchmark SLOs — Performance SLO warning (near threshold)

Suite Status
Startup 🟡 warning

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.89 s 13.87 s [-0.6%; +0.8%] (no difference)
startup:insecure-bank:tracing:Agent 12.87 s 12.89 s [-0.8%; +0.5%] (no difference)
startup:petclinic:appsec:Agent 17.75 s 17.56 s [+0.1%; +2.1%] (maybe worse)
startup:petclinic:iast:Agent 16.78 s 16.83 s [-8.2%; +7.6%] (unstable)
startup:petclinic:profiling:Agent 17.30 s 17.39 s [-1.8%; +0.8%] (no difference)
startup:petclinic:sca:Agent 17.71 s 17.67 s [-0.7%; +1.1%] (no difference)
startup:petclinic:tracing:Agent 16.69 s 16.68 s [-1.1%; +1.2%] (no difference)

Commit: 70380246 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@bric3
bric3 force-pushed the bdu/preserve-class-latch-default-methods branch from 3f106a9 to 15910e3 Compare October 12, 2026 09:56
@bric3 bric3 changed the title Prevent ClassLatch from latching wrappers with working defaults Prevent ClassLatch from latching wrappers with defaults methods Oct 12, 2026
@bric3
bric3 marked this pull request as ready for review October 12, 2026 10:12
@bric3
bric3 requested a review from a team as a code owner October 12, 2026 10:12
@bric3
bric3 requested review from PerfectSlayer and removed request for a team October 12, 2026 10:12
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T10:16:01.878777Z 15910e3 Draft marked ready
🔒 Security Review ✅ Completed 2026-10-12T10:21:29.412746Z 15910e3 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@bric3
bric3 requested a review from dougqh October 12, 2026 10:13

@datadog-prod-us1-4 datadog-prod-us1-4 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.

Bits Code Review: PASS

More details

The message-less AbstractMethodError fallback preserves wrappers with an inherited concrete default method, preventing delegate failures from disabling healthy instances of the same class. The completed static review found no reportable defect.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 15910e3

@dougqh dougqh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for fixing my oversight

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit e8bb687 into master Oct 14, 2026
609 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the bdu/preserve-class-latch-default-methods branch October 14, 2026 16:35
@github-actions github-actions Bot added this to the 1.68.0 milestone Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM type: bug fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants