Repository navigation
Conversation
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
ClassLatch attributes an AbstractMethodError to a class by parsing HotSpot's message. JDK 8 throws it with a null message once the call site has dispatched to a class that does implement the method, which in production is nearly always, so handleAbstractMethod never latched on Java 8 and every call for a deficient class kept throwing. When there is no message, attribute the error by checking whether the key class still has a public abstract method of that name, as a concrete class that never implemented an interface method does. A delegating wrapper has a concrete implementation, so an error from its delegate is still not blamed on it. The reflection runs only on the failure path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5a1c21e to
bac191e
Compare
| public static final class Response extends HttpServletExtractAdapter<HttpServletResponse> { | ||
| public static final Response GETTER = new Response(); | ||
|
|
||
| static final HeaderAccessLatch HEADER_ACCESS = new HeaderAccessLatch(); |
There was a problem hiding this comment.
To Claude, let's just call this HEADER_LATCH
|
|
||
| @Override | ||
| public void forEachKey(HttpServletResponse carrier, AgentPropagation.KeyClassifier classifier) { | ||
| final Collection<String> headerNames = HEADER_ACCESS.tryApply(carrier); |
There was a problem hiding this comment.
This latching situation isn't as straight-forward as others, but I think this application makes sense. I'm curious to hear what others think.
The latch is really driven by calls to getHeader in the loop below, but once the latch is triggered, we cut off the flow of calls to getHeaders to short-circuit the entire process.
Class.getMethods() resolves every public method's signature, so a class with a method that references a type missing from the class path raises NoClassDefFoundError. That escaped from the AbstractMethodError handler and out of tryApply, where before the null-message attribution the fallback was returned. Catch LinkageError alongside SecurityException and cache "cannot tell", so the class is not latched and the fallback is returned as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HttpServletExtractAdapter.Response called HttpServletResponse.getHeaderNames() and getHeader(...) on every response. A class compiled against Servlet 2.5 that implements HttpServletResponse itself does not implement them, so the calls threw AbstractMethodError, which escaped as "Failed to decorate span on response" and skipped the response-header callbacks for every response of that class. Guard the accessors with a ClassLatch keyed on the response class. A latched class visits no headers, so the header-done callback still fires. Subclasses of HttpServletResponseWrapper need no special handling: they inherit the container's delegating accessors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Response overrides forEachKey, so the base class's getHeaderNames/getHeader contract only served Request; move it there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bac191e to
bea559a
Compare
What Does This Do
Makes the servlet 3.0 response-header reader tolerate response classes that don't implement the Servlet 3.0 header accessors.
HttpServletExtractAdapter.ResponsecalledHttpServletResponse.getHeaderNames()/getHeader(...)on every response. A class compiled against Servlet 2.5 that implementsHttpServletResponseitself doesn't implement them, so the call throwsAbstractMethodError. That escaped throughHttpServerDecorator.callIGCallbackResponseAndHeaders, was reported as "Failed to decorate span on response", and stopped the response-header callbacks for every response of that class.Now the accessors are guarded by a
ClassLatchkeyed on the response class:ClassLatch.handleAbstractMethod(response, "getHeaderNames", ...)latches the class; agetHeaderfailure latches it the same way, throughlatchIfNamed. Either way the visit stops without re-emitting keys.Revised from the first version: this used to hand-roll the same pieces (a static tripwire, a per-class
ClassValue, a reflection probe) plus a fallback that unwrappedServletResponseWrapperto find a usable delegate.ClassLatchprovides the first three. The unwrap is dropped because it can almost never apply:HttpServletResponseWrappercomes from the container's Servlet 3.0+ jar, so a wrapper subclass compiled against 2.5 inherits working, delegating accessors and never throws. The classes that do throw implementHttpServletResponsedirectly and aren't wrappers. A test pins that inheritance.Motivation
Instrumentation telemetry shows this at
HttpServletExtractAdapter$Response.getHeaderNames:48: about 10.5k events a day, steady at 400-500 an hour, on tracers 1.58 to 1.66, mostly Java 8 services.Muzzle can't catch this. It verifies that
HttpServletResponse.getHeaderNames()exists in the API, and it does. The failure is a particular runtime implementation not honouring it.Additional Notes
Depends on #12799 (stacked on it). On Java 8,
AbstractMethodErrorhas no message once the call site has seen a working class, and without #12799ClassLatchnever latches there. The two latch tests warm the call sites with a working response first; they fail on Java 8 againstmaster'sClassLatchand pass with #12799.For the AppSec/IAST reviewers, please look at this:
responseStarted(status)fires before the header visit, so it was never affected. What was skipped is the response-header callbacks andigKeyClassifier.done()(responseHeaderDone).done()still runs with no headers. The servlet 2.x decorator instead returns anullresponseGetter(), which skips the header callbacks anddone()entirely. I went with "empty headers" soresponseHeaderDonestill fires. Happy to switch to the 2.x behaviour if AppSec prefers it.AbstractMethodErrorfrom theclassifier.acceptcallback says nothing about the response class and doesn't latch it (there's a test for this).Tests:
HttpServletExtractAdapterTest, 7 cases, using ASM-generated response classes so a realAbstractMethodErroris thrown. They cover a class missing both accessors, a class missing onlygetHeader(not producible by javac, but possible from bytecode generators), a 2.5-style wrapper subclass, healthy responses after a failure, and a callback failure. They pass on the default JDK and on Java 8. The full javax-servlet-3.0 suite passes (1,360 tests).🤖 Generated with Claude Code