Skip to content

Commit e56b048

Browse files
author
amos.chen
committed
Fix jedis-4.x-plugin double-stopping the span stack on Redis exceptions
AbstractConnectionInterceptor.handleMethodException() called ContextManager.stopSpan(span) explicitly, but afterMethod() always runs afterwards too (InstMethodsInter invokes it in a finally block on every path, including exceptions) and stops the same span again. The second stopSpan() pops whatever is now on top of the stack instead - typically the caller's entry/local span - corrupting the trace for the rest of the request. beforeMethod() also dereferenced the per-Connection dynamic field without a null check. When it is null (observed for some pooled or recycled connections not captured by the constructor interceptor), this throws before createExitSpan() runs, so afterMethod()'s stopSpan() again pops a span that was never pushed by this interceptor. Fix: only log the error in handleMethodException() (matching jedis-2.x-3.x-plugin's safe behavior) and fall back to an "unknown" peer when the dynamic field is null so the exit span is always pushed and the stack stays balanced. Resolves apache/skywalking#14085
1 parent e9b8826 commit e56b048

2 files changed

Lines changed: 19 additions & 4 deletions

File tree

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Release Notes.
55
9.8.0
66
------------------
77

8+
* Fix `jedis-4.x-plugin`'s `AbstractConnectionInterceptor` double-stopping the span stack on any
9+
Redis-level exception (or a null dynamic field on a pooled/recycled `Connection`), which corrupted
10+
the parent trace for the rest of the request (apache/skywalking#14085).
811
* Add Spring LDAP 3.3.x-4.x plugin.
912
* Exclude macOS metadata files from source and binary release archives (apache/skywalking#14080).
1013
* Fix `NoSuchMethodError: org.apache.skywalking.apm.plugin.spring.webflux.v6.DispatcherHandlerHandleMethodInterceptor`

apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v4/AbstractConnectionInterceptor.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,22 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr
5353
// Refer to `plugin.jedis.operation_mapping_read`, `plugin.jedis.operation_mapping_write` config item in agent.config
5454
String cmd = protocolCommand == null ? UNKNOWN : protocolCommand.toLowerCase();
5555
ConnectionInformation connectionData = (ConnectionInformation) objInst.getSkyWalkingDynamicField();
56+
// connectionData can be null when this Connection instance wasn't captured by the constructor
57+
// interceptor (e.g. a pooled/recycled connection created through a code path the constructor
58+
// interceptor doesn't cover). Fall back to UNKNOWN instead of throwing here: an exception in
59+
// this method, before createExitSpan() runs, would leave no exit span pushed for this call,
60+
// so afterMethod()'s unconditional stopSpan() would incorrectly pop and close whatever span
61+
// is already on the stack (typically the caller's entry/local span).
62+
String actualTarget = connectionData == null ? UNKNOWN : connectionData.getActualTarget();
63+
String clusterNodes = connectionData == null ? null : connectionData.getClusterNodes();
5664
// Use cluster information to adapt Virtual Cache if exists, otherwise use real server host
57-
String peer = StringUtil.isBlank(connectionData.getClusterNodes()) ? connectionData.getActualTarget() : connectionData.getClusterNodes();
65+
String peer = StringUtil.isBlank(clusterNodes) ? actualTarget : clusterNodes;
5866
AbstractSpan span = ContextManager.createExitSpan("Jedis/" + cmd, peer);
5967
span.setComponent(ComponentsDefine.JEDIS);
6068
readKeyIfNecessary(iterator).ifPresent(key -> Tags.CACHE_KEY.set(span, key));
6169
Tags.CACHE_CMD.set(span, cmd);
6270
Tags.CACHE_TYPE.set(span, CACHE_TYPE);
63-
TAG_ARGS.set(span, connectionData.getActualTarget());
71+
TAG_ARGS.set(span, actualTarget);
6472
parseOperation(cmd).ifPresent(op -> Tags.CACHE_OP.set(span, op));
6573
SpanLayer.asCache(span);
6674
}
@@ -84,8 +92,12 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA
8492

8593
@Override
8694
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {
87-
AbstractSpan span = ContextManager.activeSpan().log(t).errorOccurred();
88-
ContextManager.stopSpan(span);
95+
// Do not call ContextManager.stopSpan() here: afterMethod() below always runs afterwards
96+
// (InstMethodsInter invokes it in a finally block, on every path including exceptions) and
97+
// already pops this span. Stopping it a second time here pops one extra span - typically the
98+
// caller's entry/local span - corrupting the trace for the rest of the request. Only log the
99+
// exception on the still-active span, matching the jedis-2.x-3.x-plugin's safe behavior.
100+
ContextManager.activeSpan().log(t).errorOccurred();
89101
}
90102

91103
private Optional<String> parseOperation(String cmd) {

0 commit comments

Comments
 (0)