From d6c334bb1c287b21e3894b259e7d7d5d70693a18 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 3 Jun 2026 10:53:54 +0200 Subject: [PATCH] Tolerate servlet 2.x wrappers used on servlet-3 classpath for isAsyncStarted calls --- .../instrumentation/servlet3/Servlet3Advice.java | 6 +++--- .../servlet3/Servlet3Decorator.java | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-3.0/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Advice.java b/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-3.0/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Advice.java index c750c3fafc1..43ff802600c 100644 --- a/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-3.0/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Advice.java +++ b/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-3.0/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Advice.java @@ -158,7 +158,7 @@ public static void stopSpan( final AgentSpan span = spanFromContext(scope.context()); - if (request.isAsyncStarted()) { + if (DECORATE.safeIsAsyncStarted(request)) { AtomicBoolean activated = new AtomicBoolean(); FinishAsyncDispatchListener asyncListener = new FinishAsyncDispatchListener(scope, activated, !isDispatch); @@ -166,11 +166,11 @@ public static void stopSpan( request.setAttribute(DD_FIN_DISP_LIST_SPAN_ATTRIBUTE, asyncListener); try { request.getAsyncContext().addListener(asyncListener); - } catch (final IllegalStateException e) { + } catch (final IllegalStateException | AbstractMethodError ignored) { // org.eclipse.jetty.server.Request may throw an exception here if request became // finished after check above. We just ignore that exception and move on. } - if (!request.isAsyncStarted() && activated.compareAndSet(false, true)) { + if (!DECORATE.safeIsAsyncStarted(request) && activated.compareAndSet(false, true)) { if (!isDispatch) { DECORATE.onResponse(span, resp); } diff --git a/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-3.0/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Decorator.java b/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-3.0/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Decorator.java index 9db2d47d437..67332c5b581 100644 --- a/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-3.0/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Decorator.java +++ b/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-3.0/src/main/java/datadog/trace/instrumentation/servlet3/Servlet3Decorator.java @@ -10,6 +10,7 @@ import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator; import javax.servlet.ServletException; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; @@ -127,4 +128,19 @@ public AgentSpan onError(final AgentSpan span, final Throwable throwable) { } return span; } + + /** + * Safely calls ServletRequest#isAsyncStarted. Some 2.x wrappers might be dangling in the 3.x + * classpath and produce an AbstractMethodError + * + * @param servletRequest the servlet request. + * @return the real call result or false when the method does not exist in the provided object. + */ + public boolean safeIsAsyncStarted(final ServletRequest servletRequest) { + try { + return servletRequest.isAsyncStarted(); + } catch (AbstractMethodError ignored) { + return false; + } + } }