Skip to content

Commit 836ba8d

Browse files
authored
Add CompletableFuture in WellKnownClasses (#9622)
to be able to access result field in CompletableFuture JDK class we are adding a special access. The method resultNow() is the only method available to access result without side effects. It is available since JDK19.
1 parent d5bba8a commit 836ba8d

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/WellKnownClasses.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package datadog.trace.bootstrap.debugger.util;
22

3+
import static datadog.trace.api.telemetry.LogCollector.EXCLUDE_TELEMETRY;
4+
import static java.lang.invoke.MethodType.methodType;
5+
6+
import datadog.environment.JavaVirtualMachine;
37
import datadog.trace.bootstrap.debugger.CapturedContext;
8+
import java.lang.invoke.MethodHandle;
9+
import java.lang.invoke.MethodHandles;
410
import java.lang.reflect.InvocationTargetException;
511
import java.lang.reflect.Method;
612
import java.util.Arrays;
@@ -14,6 +20,7 @@
1420
import java.util.OptionalInt;
1521
import java.util.OptionalLong;
1622
import java.util.Set;
23+
import java.util.concurrent.CompletableFuture;
1724
import java.util.function.Function;
1825
import java.util.function.ToLongFunction;
1926
import org.slf4j.Logger;
@@ -144,12 +151,18 @@ public class WellKnownClasses {
144151
OPTIONALDOUBLE_SPECIAL_FIELDS = new HashMap<>();
145152
private static final Map<String, Function<Object, CapturedContext.CapturedValue>>
146153
OPTIONALLONG_SPECIAL_FIELDS = new HashMap<>();
154+
private static final Map<String, Function<Object, CapturedContext.CapturedValue>>
155+
COMPLETABLEFUTURE_SPECIAL_FIELDS = new HashMap<>();
147156

148157
static {
149158
OPTIONAL_SPECIAL_FIELDS.put("value", OptionalFields::value);
150159
OPTIONALINT_SPECIAL_FIELDS.put("value", OptionalFields::valueInt);
151160
OPTIONALDOUBLE_SPECIAL_FIELDS.put("value", OptionalFields::valueDouble);
152161
OPTIONALLONG_SPECIAL_FIELDS.put("value", OptionalFields::valueLong);
162+
if (JavaVirtualMachine.isJavaVersionAtLeast(19)) {
163+
// Future::resultNow method is available since JDK 19
164+
COMPLETABLEFUTURE_SPECIAL_FIELDS.put("result", CompletableFutureFields::result);
165+
}
153166
}
154167

155168
static {
@@ -158,6 +171,10 @@ public class WellKnownClasses {
158171
SPECIAL_TYPE_ACCESS.put(OptionalInt.class, OPTIONALINT_SPECIAL_FIELDS);
159172
SPECIAL_TYPE_ACCESS.put(OptionalDouble.class, OPTIONALDOUBLE_SPECIAL_FIELDS);
160173
SPECIAL_TYPE_ACCESS.put(OptionalLong.class, OPTIONALLONG_SPECIAL_FIELDS);
174+
if (JavaVirtualMachine.isJavaVersionAtLeast(19)) {
175+
// Future::resultNow method is available since JDK 19
176+
SPECIAL_TYPE_ACCESS.put(CompletableFuture.class, COMPLETABLEFUTURE_SPECIAL_FIELDS);
177+
}
161178
}
162179

163180
private static final Map<String, Function<Object, CapturedContext.CapturedValue>>
@@ -405,4 +422,36 @@ public static CapturedContext.CapturedValue valueLong(Object o) {
405422
"value", Long.TYPE.getTypeName(), ((OptionalLong) o).orElse(0L));
406423
}
407424
}
425+
426+
private static class CompletableFutureFields {
427+
private static final MethodHandle RESULT_NOW;
428+
429+
static {
430+
MethodHandle methodHandle = null;
431+
try {
432+
MethodHandles.Lookup lookup = MethodHandles.lookup();
433+
methodHandle =
434+
lookup.findVirtual(CompletableFuture.class, "resultNow", methodType(Object.class));
435+
} catch (Exception e) {
436+
LOGGER.debug(EXCLUDE_TELEMETRY, "Looking up CompletableFuture::resultNow failed: ", e);
437+
}
438+
RESULT_NOW = methodHandle;
439+
}
440+
441+
public static CapturedContext.CapturedValue result(Object o) {
442+
if (RESULT_NOW == null) {
443+
throw new UnsupportedOperationException("CompletableFuture::resultNow not available");
444+
}
445+
try {
446+
CompletableFuture<?> future = (CompletableFuture<?>) o;
447+
// need to check with isDone() to avoid getting exception if null.
448+
// Known benign rare race condition result != null => result == null
449+
// between isDone() and resultNow()
450+
Object result = future.isDone() ? RESULT_NOW.invokeExact((future)) : null;
451+
return CapturedContext.CapturedValue.of("result", Object.class.getTypeName(), result);
452+
} catch (Throwable t) {
453+
throw new RuntimeException(t);
454+
}
455+
}
456+
}
408457
}

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.datadog.debugger.util.MoshiSnapshotHelper;
3131
import com.datadog.debugger.util.MoshiSnapshotTestHelper;
3232
import com.squareup.moshi.JsonAdapter;
33+
import datadog.environment.JavaVirtualMachine;
3334
import datadog.trace.bootstrap.debugger.CapturedContext;
3435
import datadog.trace.bootstrap.debugger.CapturedStackFrame;
3536
import datadog.trace.bootstrap.debugger.DebuggerContext;
@@ -63,6 +64,7 @@
6364
import java.util.OptionalLong;
6465
import java.util.Random;
6566
import java.util.UUID;
67+
import java.util.concurrent.CompletableFuture;
6668
import java.util.concurrent.atomic.AtomicLong;
6769
import java.util.concurrent.locks.LockSupport;
6870
import org.junit.jupiter.api.Assertions;
@@ -306,6 +308,7 @@ static class WellKnownClasses {
306308
StackTraceElement element = new StackTraceElement("Foo", "bar", "foo.java", 42);
307309
File file = new File("/tmp/foo");
308310
Path path = file.toPath();
311+
CompletableFuture<String> future = CompletableFuture.completedFuture("FutureCompleted!");
309312
}
310313

311314
@Test
@@ -385,6 +388,12 @@ public void wellKnownClasses() throws IOException {
385388
assertPrimitiveValue(objLocalFields, "file", File.class.getTypeName(), "/tmp/foo");
386389
// path
387390
assertPrimitiveValue(objLocalFields, "path", "sun.nio.fs.UnixPath", "/tmp/foo");
391+
if (JavaVirtualMachine.isJavaVersionAtLeast(19)) {
392+
Map<String, Object> future = (Map<String, Object>) objLocalFields.get("future");
393+
assertComplexClass(future, CompletableFuture.class.getTypeName());
394+
Map<String, Object> futureFields = (Map<String, Object>) future.get(FIELDS);
395+
assertPrimitiveValue(futureFields, "result", String.class.getTypeName(), "FutureCompleted!");
396+
}
388397
}
389398

390399
@Test

0 commit comments

Comments
 (0)