Fix redisson ClassCastException
MODIFY
instrumentation/redisson-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/CompletableFutureWrapper.java
instrumentation/redisson-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/CompletableFutureWrapper.java
@@ -14,17 +14,20 @@ public final class CompletableFutureWrapper<T> extends CompletableFuture<T>
private volatile EndOperationListener<T> endOperationListener;
private CompletableFutureWrapper(CompletableFuture<T> delegate) {
+ Context context = Context.current();
this.whenComplete(
(result, error) -> {
EndOperationListener<T> endOperationListener = this.endOperationListener;
if (endOperationListener != null) {
endOperationListener.accept(result, error);
}
+ try (Scope ignored = context.makeCurrent()) {
if (error != null) {
delegate.completeExceptionally(error);
} else {
delegate.complete(result);
}
+ }
});
}
@@ -40,33 +43,6 @@ public final class CompletableFutureWrapper<T> extends CompletableFuture<T>
return new CompletableFutureWrapper<>(delegate);
}
- /**
- * Wrap {@link CompletableFuture} to run callbacks with the context that was current at the time
- * this method was called.
- *
- * <p>This method should be called on, or as close as possible to, the {@link CompletableFuture}
- * that is returned to the user to ensure that the callbacks added by user are run in appropriate
- * context.
- */
- public static <T> CompletableFuture<T> wrapContext(CompletableFuture<T> future) {
- Context context = Context.current();
- // when input future is completed, complete result future with context that was current
- // at the time when the future was wrapped
- CompletableFuture<T> result = new CompletableFuture<>();
- future.whenComplete(
- (T value, Throwable throwable) -> {
- try (Scope ignored = context.makeCurrent()) {
- if (throwable != null) {
- result.completeExceptionally(throwable);
- } else {
- result.complete(value);
- }
- }
- });
-
- return result;
- }
-
@Override
public void setEndOperationListener(EndOperationListener<T> endOperationListener) {
this.endOperationListener = endOperationListener;

DELETE
instrumentation/redisson-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/RedisCommandAsyncServiceInstrumentation.java
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.redisson;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
-
-import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
-import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
-import net.bytebuddy.asm.Advice;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.matcher.ElementMatcher;
-import org.redisson.misc.RPromise;
-
-public class RedisCommandAsyncServiceInstrumentation implements TypeInstrumentation {
- @Override
- public ElementMatcher<TypeDescription> typeMatcher() {
- return named("org.redisson.command.CommandAsyncService");
- }
-
- @Override
- public void transform(TypeTransformer transformer) {
- // used before 3.16.8
- transformer.applyAdviceToMethod(
- named("async").and(takesArgument(5, named("org.redisson.misc.RPromise"))),
- this.getClass().getName() + "$WrapPromiseAdvice");
- }
-
- @SuppressWarnings("unused")
- public static class WrapPromiseAdvice {
-
- @Advice.OnMethodEnter(suppress = Throwable.class)
- public static void onEnter(@Advice.Argument(value = 5, readOnly = false) RPromise<?> promise) {
- promise = RedissonPromiseWrapper.wrapContext(promise);
- }
- }
-}

DELETE
instrumentation/redisson-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/RedissonCompletableFutureWrapperInstrumentation.java
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.redisson;
-
-import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
-
-import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
-import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import net.bytebuddy.asm.Advice;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.matcher.ElementMatcher;
-
-public class RedissonCompletableFutureWrapperInstrumentation implements TypeInstrumentation {
- @Override
- public ElementMatcher<TypeDescription> typeMatcher() {
- return named("org.redisson.misc.CompletableFutureWrapper");
- }
-
- @Override
- public void transform(TypeTransformer transformer) {
- // used since 3.16.8
- transformer.applyAdviceToMethod(
- isConstructor().and(takesArgument(0, CompletionStage.class)),
- this.getClass().getName() + "$WrapCompletionStageAdvice");
- transformer.applyAdviceToMethod(
- isConstructor().and(takesArgument(0, CompletableFuture.class)),
- this.getClass().getName() + "$WrapCompletableFutureAdvice");
- }
-
- @SuppressWarnings("unused")
- public static class WrapCompletableFutureAdvice {
-
- @Advice.OnMethodEnter(suppress = Throwable.class)
- public static void onEnter(
- @Advice.Argument(value = 0, readOnly = false) CompletableFuture<?> completableFuture) {
- completableFuture = CompletableFutureWrapper.wrapContext(completableFuture);
- }
- }
-
- @SuppressWarnings("unused")
- public static class WrapCompletionStageAdvice {
-
- @Advice.OnMethodEnter(suppress = Throwable.class)
- public static void onEnter(
- @Advice.Argument(value = 0, readOnly = false) CompletionStage<?> completionStage) {
- completionStage = CompletableFutureWrapper.wrapContext(completionStage.toCompletableFuture());
- }
- }
-}

MODIFY
instrumentation/redisson-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/RedissonInstrumentationModule.java
instrumentation/redisson-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/RedissonInstrumentationModule.java
@@ -21,10 +21,6 @@ public class RedissonInstrumentationModule extends InstrumentationModule {
@Override
public List<TypeInstrumentation> typeInstrumentations() {
- return asList(
- new RedisConnectionInstrumentation(),
- new RedisCommandDataInstrumentation(),
- new RedisCommandAsyncServiceInstrumentation(),
- new RedissonCompletableFutureWrapperInstrumentation());
+ return asList(new RedisConnectionInstrumentation(), new RedisCommandDataInstrumentation());
}
}

MODIFY
instrumentation/redisson-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/RedissonPromiseWrapper.java
instrumentation/redisson-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/RedissonPromiseWrapper.java
@@ -14,17 +14,20 @@ public class RedissonPromiseWrapper<T> extends RedissonPromise<T> implements Pro
private volatile EndOperationListener<T> endOperationListener;
private RedissonPromiseWrapper(RPromise<T> delegate) {
+ Context context = Context.current();
this.whenComplete(
(result, error) -> {
EndOperationListener<T> endOperationListener = this.endOperationListener;
if (endOperationListener != null) {
endOperationListener.accept(result, error);
}
+ try (Scope ignored = context.makeCurrent()) {
if (error != null) {
delegate.tryFailure(error);
} else {
delegate.trySuccess(result);
}
+ }
});
}
@@ -40,32 +43,6 @@ public class RedissonPromiseWrapper<T> extends RedissonPromise<T> implements Pro
return new RedissonPromiseWrapper<>(delegate);
}
- /**
- * Wrap {@link RPromise} to run callbacks with the context that was current at the time this
- * method was called.
- *
- * <p>This method should be called on, or as close as possible to, the {@link RPromise} that is
- * returned to the user to ensure that the callbacks added by user are run in appropriate context.
- */
- public static <T> RPromise<T> wrapContext(RPromise<T> promise) {
- Context context = Context.current();
- // when returned promise is completed, complete input promise with context that was current
- // at the time when the promise was wrapped
- RPromise<T> result = new RedissonPromise<T>();
- result.whenComplete(
- (value, error) -> {
- try (Scope ignored = context.makeCurrent()) {
- if (error != null) {
- promise.tryFailure(error);
- } else {
- promise.trySuccess(value);
- }
- }
- });
-
- return result;
- }
-
@Override
public void setEndOperationListener(EndOperationListener<T> endOperationListener) {
this.endOperationListener = endOperationListener;

MODIFY
instrumentation/redisson-3.0/javaagent/src/test/groovy/RedissonAsyncClientTest.groovy
instrumentation/redisson-3.0/javaagent/src/test/groovy/RedissonAsyncClientTest.groovy
import io.opentelemetry.api.trace.Span
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
+import java.util.concurrent.Callable
import java.util.concurrent.CompletionStage
import org.redisson.Redisson
import org.redisson.api.RBucket
import org.redisson.api.RFuture
+import org.redisson.api.RScheduledExecutorService
import org.redisson.api.RSet
import org.redisson.api.RedissonClient
import org.redisson.config.Config
@@ -129,5 +131,21 @@ class RedissonAsyncClientTest extends AgentInstrumentationSpecification {
}
}
+ // regression test for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/6033
+ def "test schedule"() {
+ RScheduledExecutorService executorService = redisson.getExecutorService("EXECUTOR")
+ def taskId = executorService.schedule(new MyCallable(), 0, TimeUnit.SECONDS)
+ .getTaskId()
+ expect:
+ taskId != null
+ }
+
+ private static class MyCallable implements Callable, Serializable {
+
+ @Override
+ Object call() throws Exception {
+ return null
+ }
+ }
}

Fix redisson ClassCastException (#6054)