Skip to content

FFI/FFM : Unexptected behaviour when using Memory Segment for upcall/downcall #22573

@r30shah

Description

@r30shah

While looking into one of the test case failure within jextract [1], I found that when a memory segment allocated using Arena.ofConfined() [2] was used to pass to make a downcall, JVM did not throw java.lang.WrongThreadException as expected. Here is the simple unit test to reproduce this behavior.

1. Java Test Case : ConfinedMemorySegmentDowncallTest.java
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.SymbolLookup;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_CHAR;
import static java.lang.foreign.ValueLayout.JAVA_DOUBLE;
import static java.lang.foreign.ValueLayout.JAVA_FLOAT;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
import static java.lang.foreign.ValueLayout.JAVA_SHORT;
import java.lang.invoke.MethodHandle;
public class ConfinedMemorySegmentDowncallTest {
	static {
		System.loadLibrary("test");
	}
	private static Linker linker = Linker.nativeLinker();
	private static final SymbolLookup nativeLibLookup = SymbolLookup.loaderLookup();
	private static final SymbolLookup defaultLibLookup = linker.defaultLookup();
	private static Arena confinedArena;
	public static MemorySegment segment;

	public static void makeDownCall() throws Throwable {
		FunctionDescriptor fd = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT);
		MemorySegment functionSymbol = nativeLibLookup.find("addIntsFromArray").get();
		MethodHandle mh = linker.downcallHandle(functionSymbol, fd);
		int result = (int)mh.invokeExact(segment, 5);
		System.out.println(Thread.currentThread().getName()+" result = "+result);
	}


	public static void main(String[] string) {
		try {
			confinedArena = Arena.ofConfined();
			segment = confinedArena.allocate(ValueLayout.JAVA_INT, 5);
			for (int j=0; j < 5; j++) {
				segment.set(ValueLayout.JAVA_INT, j * ValueLayout.JAVA_INT.byteSize(), (j+1)*10);
			}
			makeDownCall();
		} catch (Throwable ex) {
			ex.printStackTrace();
		}

		Thread thread = new Thread(() -> {
			try {
				makeDownCall();
			} catch (Throwable ex) {
				ex.printStackTrace();
			}
		});

		thread.start();
	}
}
2. Native C Code ``` int addIntsFromArray(int *ptr , int elements) { int sum = 0; for (int i=0; i < elements; i++) { sum += ptr[i]; } return sum; } ```
3. Compile and build test ``` $ /bin/javac ConfinedMemorySegmentDowncallTest.java $ gcc -shared -fPIC -o libtest.so test.c ```

Output using Temurin

<temuring-jdk-24>/bin/java --enable-native-access=ALL-UNNAMED  -Djava.library.path=`pwd` -cp . ConfinedMemorySegmentDowncallTest
main result = 150
java.lang.WrongThreadException: Attempted access outside owning thread
	at java.base/jdk.internal.foreign.MemorySessionImpl.wrongThread(MemorySessionImpl.java:322)
	at java.base/jdk.internal.misc.ScopedMemoryAccess$ScopedAccessError.newRuntimeException(ScopedMemoryAccess.java:114)
	at java.base/jdk.internal.foreign.MemorySessionImpl.checkValidState(MemorySessionImpl.java:217)
	at java.base/jdk.internal.foreign.ConfinedSession.acquire0(ConfinedSession.java:53)
	at ConfinedMemorySegmentDowncallTest.makeDownCall(ConfinedMemorySegmentDowncallTest.java:31)
	at ConfinedMemorySegmentDowncallTest.lambda$main$0(ConfinedMemorySegmentDowncallTest.java:50)
	at java.base/java.lang.Thread.run(Thread.java:1447)

Output using OpenJ9

<openj9-jdk24>/bin/java --enable-native-access=ALL-UNNAMED  -Djava.library.path=`pwd` -cp . ConfinedMemorySegmentDowncallTest
main result = 150
Thread-0 result = 150

Following code behaves same with OpenJ9 and Temuring build (i.e, I see both OpenJ9 and Temurin throwing WrongThreadAcception when other thread then owner thread is trying to read from it.

ConfinedMemorySegmentTest.java ``` import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout;

public class ConfinedMemorySegmentTest {
private static Arena confinedArena;
public static MemorySegment segment;

public static void readSegment(int size) throws Throwable {
	for(int j = 0; j < size; j++) {
		int value = segment.get(ValueLayout.JAVA_INT, j * ValueLayout.JAVA_INT.byteSize());
		System.out.println(Thread.currentThread().getName()+" value read = "+value);
	}
}

public static void main(String[] string) {
	try {
		confinedArena = Arena.ofConfined();
		segment = confinedArena.allocate(ValueLayout.JAVA_INT, 5);
		for (int j=0; j < 5; j++) {
			segment.set(ValueLayout.JAVA_INT, j * ValueLayout.JAVA_INT.byteSize(), (j+1)*10);
		}
		readSegment(5);
	} catch (Throwable ex) {
		ex.printStackTrace();
	}

	Thread thread = new Thread(() -> {
		try {
			readSegment(5);
		} catch (Throwable ex) {
			ex.printStackTrace();
		}
	});

	thread.start();
}

}

</details>





[1]. https://github.com/openjdk/jextract/blob/267a0abe441ba9bf2da3cfebd3dfd05c26bf464c/test/jtreg/generator/reachableException/TestReachableException.java#L61
[2]. https://github.com/ibmruntimes/openj9-openjdk-jdk24/blob/905cf12f2c9fa3c4515234d5f07d14d9d4356cfb/src/java.base/share/classes/java/lang/foreign/Arena.java#L256-L258

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions