From fcc80370517a404383e76192f886ae64ebb0af1b Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Thu, 8 Jan 2026 09:29:35 +0100 Subject: [PATCH 1/2] Defer FFM methodhandles resolutions --- .../servicediscovery/MemFDUnixWriterFFM.java | 117 +++++++++++------- 1 file changed, 70 insertions(+), 47 deletions(-) diff --git a/dd-java-agent/agent-tooling/src/main/java25/datadog/trace/agent/tooling/servicediscovery/MemFDUnixWriterFFM.java b/dd-java-agent/agent-tooling/src/main/java25/datadog/trace/agent/tooling/servicediscovery/MemFDUnixWriterFFM.java index 27c3a8f3c44..65601a8659d 100644 --- a/dd-java-agent/agent-tooling/src/main/java25/datadog/trace/agent/tooling/servicediscovery/MemFDUnixWriterFFM.java +++ b/dd-java-agent/agent-tooling/src/main/java25/datadog/trace/agent/tooling/servicediscovery/MemFDUnixWriterFFM.java @@ -20,65 +20,82 @@ public class MemFDUnixWriterFFM extends MemFDUnixWriter { private static final long ERRNO_OFFSET = CAPTURE_STATE_LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("errno")); - // Function handles - initialized once - private final MethodHandle syscallMH; - private final MethodHandle writeMH; - private final MethodHandle fcntlMH; + private static class Lazy { + // Function handles - initialized once + static final MethodHandle syscallMH; + static final MethodHandle writeMH; + static final MethodHandle fcntlMH; + + static { + MethodHandle syscall = null; + MethodHandle write = null; + MethodHandle fcntl = null; + try { + final Linker linker = Linker.nativeLinker(); + final SymbolLookup LIBC = linker.defaultLookup(); + // long syscall(long number, ...) + // Note: variadic functions require special handling, we'll use a fixed signature + syscall = + linker.downcallHandle( + LIBC.find("syscall").orElseThrow(), + FunctionDescriptor.of( + ValueLayout.JAVA_LONG, // return type: long + ValueLayout.JAVA_LONG, // syscall number + ValueLayout.ADDRESS, // const char* name + ValueLayout.JAVA_INT // int flags + ), + Linker.Option.captureCallState("errno")); + + // ssize_t write(int fd, const void *buf, size_t count) + write = + linker.downcallHandle( + LIBC.find("write").orElseThrow(), + FunctionDescriptor.of( + ValueLayout.JAVA_LONG, // return type: ssize_t + ValueLayout.JAVA_INT, // int fd + ValueLayout.ADDRESS, // const void* buf + ValueLayout.JAVA_LONG // size_t count + ), + Linker.Option.captureCallState("errno")); + + // int fcntl(int fd, int cmd, ... /* arg */) + fcntl = + linker.downcallHandle( + LIBC.find("fcntl").orElseThrow(), + FunctionDescriptor.of( + ValueLayout.JAVA_INT, // return type: int + ValueLayout.JAVA_INT, // int fd + ValueLayout.JAVA_INT, // int cmd + ValueLayout.JAVA_INT // int arg + ), + Linker.Option.captureCallState("errno")); + } catch (final Throwable ex) { + log.error("Failed to initialize MemFDUnixWriterFFM", ex); + } finally { + syscallMH = syscall; + writeMH = write; + fcntlMH = fcntl; + } + } + } private final MemorySegment captureState; public MemFDUnixWriterFFM() { - final Linker linker = Linker.nativeLinker(); - final SymbolLookup LIBC = linker.defaultLookup(); - // Allocate memory for capturing errno (need to be alive until the class instance is collected) this.captureState = Arena.ofAuto().allocate(CAPTURE_STATE_LAYOUT); - - // long syscall(long number, ...) - // Note: variadic functions require special handling, we'll use a fixed signature - syscallMH = - linker.downcallHandle( - LIBC.find("syscall").orElseThrow(), - FunctionDescriptor.of( - ValueLayout.JAVA_LONG, // return type: long - ValueLayout.JAVA_LONG, // syscall number - ValueLayout.ADDRESS, // const char* name - ValueLayout.JAVA_INT // int flags - ), - Linker.Option.captureCallState("errno")); - - // ssize_t write(int fd, const void *buf, size_t count) - writeMH = - linker.downcallHandle( - LIBC.find("write").orElseThrow(), - FunctionDescriptor.of( - ValueLayout.JAVA_LONG, // return type: ssize_t - ValueLayout.JAVA_INT, // int fd - ValueLayout.ADDRESS, // const void* buf - ValueLayout.JAVA_LONG // size_t count - ), - Linker.Option.captureCallState("errno")); - - // int fcntl(int fd, int cmd, ... /* arg */) - fcntlMH = - linker.downcallHandle( - LIBC.find("fcntl").orElseThrow(), - FunctionDescriptor.of( - ValueLayout.JAVA_INT, // return type: int - ValueLayout.JAVA_INT, // int fd - ValueLayout.JAVA_INT, // int cmd - ValueLayout.JAVA_INT // int arg - ), - Linker.Option.captureCallState("errno")); } @Override protected long syscall(long number, String name, int flags) { + if (Lazy.syscallMH == null) { + return -1; + } try (Arena arena = Arena.ofConfined()) { // Allocate native string for file name MemorySegment fileNameSegment = arena.allocateFrom(name); // Call memfd_create via syscall, passing captureState as first arg - return (long) syscallMH.invoke(captureState, (long) number, fileNameSegment, flags); + return (long) Lazy.syscallMH.invoke(captureState, (long) number, fileNameSegment, flags); } catch (Throwable t) { log.error("Unable to make a syscall through FFM", t); return -1; @@ -87,13 +104,16 @@ protected long syscall(long number, String name, int flags) { @Override protected long write(int fd, byte[] payload) { + if (Lazy.writeMH == null) { + return -1; + } try (Arena arena = Arena.ofConfined()) { // Allocate native memory for payload MemorySegment buffer = arena.allocate(payload.length); MemorySegment.copy(payload, 0, buffer, ValueLayout.JAVA_BYTE, 0, payload.length); // Write payload to memfd, passing captureState as first arg - return (long) writeMH.invoke(captureState, fd, buffer, (long) payload.length); + return (long) Lazy.writeMH.invoke(captureState, fd, buffer, (long) payload.length); } catch (Throwable t) { log.error("Unable to make a write call through FFM", t); return -1; @@ -102,8 +122,11 @@ protected long write(int fd, byte[] payload) { @Override protected int fcntl(int fd, int cmd, int arg) { + if (Lazy.fcntlMH == null) { + return -1; + } try { - return (int) fcntlMH.invoke(captureState, fd, cmd, arg); + return (int) Lazy.fcntlMH.invoke(captureState, fd, cmd, arg); } catch (Throwable t) { log.error("Unable to make a fcntl call through FFM", t); return -1; From 520986d07b4a6b5d9bbc92991b4b57a313215502 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Thu, 8 Jan 2026 10:52:07 +0100 Subject: [PATCH 2/2] suggestions --- .../servicediscovery/MemFDUnixWriterFFM.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/dd-java-agent/agent-tooling/src/main/java25/datadog/trace/agent/tooling/servicediscovery/MemFDUnixWriterFFM.java b/dd-java-agent/agent-tooling/src/main/java25/datadog/trace/agent/tooling/servicediscovery/MemFDUnixWriterFFM.java index 65601a8659d..602fb1723d8 100644 --- a/dd-java-agent/agent-tooling/src/main/java25/datadog/trace/agent/tooling/servicediscovery/MemFDUnixWriterFFM.java +++ b/dd-java-agent/agent-tooling/src/main/java25/datadog/trace/agent/tooling/servicediscovery/MemFDUnixWriterFFM.java @@ -32,12 +32,12 @@ private static class Lazy { MethodHandle fcntl = null; try { final Linker linker = Linker.nativeLinker(); - final SymbolLookup LIBC = linker.defaultLookup(); + final SymbolLookup lookup = linker.defaultLookup(); // long syscall(long number, ...) // Note: variadic functions require special handling, we'll use a fixed signature syscall = linker.downcallHandle( - LIBC.find("syscall").orElseThrow(), + lookup.find("syscall").orElseThrow(), FunctionDescriptor.of( ValueLayout.JAVA_LONG, // return type: long ValueLayout.JAVA_LONG, // syscall number @@ -49,7 +49,7 @@ private static class Lazy { // ssize_t write(int fd, const void *buf, size_t count) write = linker.downcallHandle( - LIBC.find("write").orElseThrow(), + lookup.find("write").orElseThrow(), FunctionDescriptor.of( ValueLayout.JAVA_LONG, // return type: ssize_t ValueLayout.JAVA_INT, // int fd @@ -61,7 +61,7 @@ private static class Lazy { // int fcntl(int fd, int cmd, ... /* arg */) fcntl = linker.downcallHandle( - LIBC.find("fcntl").orElseThrow(), + lookup.find("fcntl").orElseThrow(), FunctionDescriptor.of( ValueLayout.JAVA_INT, // return type: int ValueLayout.JAVA_INT, // int fd @@ -77,6 +77,11 @@ private static class Lazy { fcntlMH = fcntl; } } + + static boolean isAvailable() { + // just check the first - either all null or all non-null. + return syscallMH != null; + } } private final MemorySegment captureState; @@ -88,7 +93,7 @@ public MemFDUnixWriterFFM() { @Override protected long syscall(long number, String name, int flags) { - if (Lazy.syscallMH == null) { + if (!Lazy.isAvailable()) { return -1; } try (Arena arena = Arena.ofConfined()) { @@ -104,7 +109,7 @@ protected long syscall(long number, String name, int flags) { @Override protected long write(int fd, byte[] payload) { - if (Lazy.writeMH == null) { + if (!Lazy.isAvailable()) { return -1; } try (Arena arena = Arena.ofConfined()) { @@ -122,7 +127,7 @@ protected long write(int fd, byte[] payload) { @Override protected int fcntl(int fd, int cmd, int arg) { - if (Lazy.fcntlMH == null) { + if (!Lazy.isAvailable()) { return -1; } try { @@ -135,6 +140,9 @@ protected int fcntl(int fd, int cmd, int arg) { @Override protected int getLastError() { + if (!Lazy.isAvailable()) { + return -1; + } try { // Read errno from the captured state memory segment return captureState.get(ValueLayout.JAVA_INT, ERRNO_OFFSET);