diff --git a/docs/user/Embedding-Native-Extensions.md b/docs/user/Embedding-Native-Extensions.md index eaa31c0cd3..c413e66c6f 100644 --- a/docs/user/Embedding-Native-Extensions.md +++ b/docs/user/Embedding-Native-Extensions.md @@ -13,6 +13,13 @@ The Context API allows you to set options such as `allowIO`, `allowHostAccess`, To use Python native extensions on GraalPy, the `allowNativeAccess` option must be set to `true`, but this opens the door to full native access. This means that while Python code may be denied access to the host file system, thread or subprocess creation, and more, the native extension is under no such restriction. +## Java Virtual Threads + +Python native extensions are not compatible with Java virtual threads. +Native extensions commonly use native thread-local state, including CPython and extension-runtime state that cannot track Java virtual-thread scheduling. +If an embedded application may load or call native extensions, run that Python code on platform threads. +For example, server applications that use virtual threads for request handling should dispatch GraalPy calls that may use native extensions to a platform-thread executor. + ## Memory Management Python C extensions, like the CPython reference implementation, use reference counting for memory management. diff --git a/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java b/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java index d9b6ed0b26..f5622e81a4 100644 --- a/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java +++ b/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java @@ -1502,6 +1502,11 @@ private void generateNativeAccessSupport(Element[] origins) throws IOException { lines.add(" return Linker.nativeLinker().upcallStub(staticMethodHandle, functionDescriptor, (Arena) arena).address();"); lines.add(" }"); lines.add(""); + lines.add(" @Override"); + lines.add(" protected boolean isCurrentThreadVirtualImpl() {"); + lines.add(" return Thread.currentThread().isVirtual();"); + lines.add(" }"); + lines.add(""); lines.add(" private static FunctionDescriptor createFunctionDescriptor(NativeSimpleType resType, NativeSimpleType[] argTypes) {"); lines.add(" MemoryLayout[] argLayouts = new MemoryLayout[argTypes.length];"); lines.add(" for (int i = 0; i < argTypes.length; i++) {"); @@ -1598,6 +1603,11 @@ private void generateDummyNativeAccessSupport(Element[] origins) throws IOExcept lines.add(" protected long createClosureImpl(MethodHandle staticMethodHandle, NativeSimpleType resType, NativeSimpleType[] argTypes, Object arena) {"); lines.add(" throw unsupported();"); lines.add(" }"); + lines.add(""); + lines.add(" @Override"); + lines.add(" protected boolean isCurrentThreadVirtualImpl() {"); + lines.add(" return false;"); + lines.add(" }"); lines.add("}"); var file = processingEnv.getFiler().createSourceFile(NATIVE_ACCESS_PACKAGE + "." + NATIVE_ACCESS_SUPPORT_IMPL_CLASS_NAME, origins); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index 7dee2c1dee..3ae238e251 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -609,7 +609,7 @@ private BackgroundGCTask(PythonContext context) { super("Python GC", LOGGER); this.ctx = new WeakReference<>(context); this.rssInterval = context.getOption(PythonOptions.BackgroundGCTaskInterval); - this.gcRSSThreshold = context.getOption(PythonOptions.BackgroundGCTaskThreshold) / (double) 100; + this.gcGrowthThreshold = context.getOption(PythonOptions.BackgroundGCTaskThreshold) / (double) 100; this.gcRSSMinimum = context.getOption(PythonOptions.BackgroundGCTaskMinimum); } @@ -624,7 +624,7 @@ private BackgroundGCTask(PythonContext context) { // RSS monitor interval in ms final int rssInterval; /** - * RSS percentage increase between System.gc() calls. Low percentage will trigger + * Resources percentage increase between System.gc() calls. Low percentage will trigger * System.gc() more often which can cause unnecessary overhead. * *
*/
- final double gcRSSThreshold;
+ final double gcGrowthThreshold;
/**
* RSS minimum memory (in megabytes) start calling System.gc(). Default is 4GB.
@@ -695,10 +695,6 @@ private void perform() {
return;
}
- if (rss < gcRSSMinimum) {
- return;
- }
-
// skip GC if no new native weakrefs have been created.
int currentWeakrefCount = context.handleContext.nativeLookup.size();
if (currentWeakrefCount < this.previousWeakrefCount || this.previousWeakrefCount == -1) {
@@ -706,8 +702,13 @@ private void perform() {
return;
}
- double ratio = ((rss - this.previousRSS) / (double) this.previousRSS);
- if (ratio >= gcRSSThreshold) {
+ double ratio = ((currentWeakrefCount - this.previousWeakrefCount) / (double) Math.max(1, this.previousWeakrefCount));
+ if (rss < gcRSSMinimum && ratio < gcGrowthThreshold) {
+ return;
+ }
+
+ ratio = ((rss - this.previousRSS) / (double) this.previousRSS);
+ if (ratio >= gcGrowthThreshold) {
this.previousWeakrefCount = currentWeakrefCount;
long start = System.nanoTime();
@@ -729,7 +730,7 @@ private void perform() {
* mappings (RssFile) and shmem memory (RssShmem). GC can only reduce RssAnon while
* RssFile is managed by the operating system which doesn't go down easily.
*/
- this.previousRSS += (long) (this.previousRSS * gcRSSThreshold);
+ this.previousRSS += (long) (this.previousRSS * gcGrowthThreshold);
}
}
}
@@ -890,6 +891,9 @@ private static CApiContext loadCApi(Node node, PythonContext context, TruffleStr
String libName = PythonContext.getSupportLibName("python-native");
final TruffleFile capiFile = homePath.resolve(libName).getCanonicalFile();
try {
+ if (PythonContext.isCurrentThreadVirtual()) {
+ throw new ApiInitException(ErrorMessages.NATIVE_EXTENSIONS_VIRTUAL_THREAD);
+ }
boolean useNative = true;
boolean isolateNative = PythonOptions.IsolateNativeModules.getValue(env.getOptions());
final NativeLibraryLocator loc;
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java
index 72ce90563f..a65a2ccd86 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java
@@ -1305,6 +1305,8 @@ public abstract class ErrorMessages {
public static final TruffleString NFI_NOT_AVAILABLE = tsLiteral("GraalPy option '%s' is set to '%s, but the 'nfi' language, which is required for this feature, is not available. " +
"If this is a GraalPy standalone distribution: this indicates internal error. If GraalPy was used as a Maven dependency: " +
"are you missing a runtime dependency 'org.graalvm.truffle:truffle-nfi-libffi', which should be a dependency of 'org.graalvm.polyglot:python{-community}'?");
+ public static final TruffleString NATIVE_EXTENSIONS_VIRTUAL_THREAD = tsLiteral("Python native extensions cannot be used from Java virtual threads. " +
+ "Run Python code that may load or call native extensions on a platform thread.");
// AST Validator
public static final TruffleString ANN_ASSIGN_WITH_SIMPLE_NON_NAME_TARGET = tsLiteral("AnnAssign with simple non-Name target");
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java
index f285b99d13..b7e3c64405 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java
@@ -171,6 +171,7 @@
import com.oracle.graal.python.runtime.exception.PythonExitException;
import com.oracle.graal.python.runtime.exception.PythonThreadKillException;
import com.oracle.graal.python.runtime.locale.PythonLocale;
+import com.oracle.graal.python.runtime.nativeaccess.NativeAccessSupport;
import com.oracle.graal.python.runtime.nativeaccess.NativeContext;
import com.oracle.graal.python.runtime.nativeaccess.NativeMemory;
import com.oracle.graal.python.runtime.object.IDUtils;
@@ -247,6 +248,10 @@ public final class PythonContext extends Python3Core {
// Used for testing only.
public boolean wasStackWalk;
+ public static boolean isCurrentThreadVirtual() {
+ return NativeAccessSupport.isCurrentThreadVirtual();
+ }
+
@TruffleBoundary
public static String getSupportLibName(PythonOS os, String libName) {
// note: this should be aligned with MX's "lib" substitution
@@ -2722,7 +2727,7 @@ public void initializeMultiThreading() {
public void attachThread(Thread thread, ContextThreadLocal threadState) {
CompilerAsserts.neverPartOfCompilation();
PythonThreadState pythonThreadState = threadState.get(thread);
- threadStateMapping.put(thread, pythonThreadState);
+ PythonThreadState previousThreadState = threadStateMapping.put(thread, pythonThreadState);
ReentrantLock initLock = getcApiInitializationLock();
/*
* Synchronize with C API initialization so that we do not miss eager initialization of this
@@ -2734,9 +2739,19 @@ public void attachThread(Thread thread, ContextThreadLocal th
initLock.lock();
try {
if (getCApiState() == CApiState.INITIALIZED) {
+ if (isCurrentThreadVirtual()) {
+ throw PRaiseNode.raiseStatic(getLanguage().unavailableSafepointLocation, SystemError, ErrorMessages.NATIVE_EXTENSIONS_VIRTUAL_THREAD);
+ }
// initialize this thread's native TLS slot eagerly instead of on first use
initializeNativeThreadState(pythonThreadState);
}
+ } catch (PException e) {
+ if (previousThreadState == null) {
+ threadStateMapping.remove(thread);
+ } else {
+ threadStateMapping.put(thread, previousThreadState);
+ }
+ throw e;
} finally {
initLock.unlock();
}
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java
index fc8c4d05a5..912b41aa90 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java
@@ -381,8 +381,19 @@ public static void checkBytecodeDSLEnv() {
@Option(category = OptionCategory.INTERNAL, usageSyntax = "