Skip to content

Commit 63bf831

Browse files
msimacekansalond
authored andcommitted
[GR-56921] Backport: Add an option to warn when C extensions are loaded
PullRequest: graalpython/3436
2 parents 7181ef6 + ddd429c commit 63bf831

File tree

9 files changed

+72
-6
lines changed

9 files changed

+72
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ language runtime. The main focus is on user-observable behavior of the engine.
77
* Updated developer metadata of Maven artifacts.
88

99
## Version 24.1.0
10+
* GraalPy is now considered stable for pure Python workloads. While many workloads involving native extension modules work, we continue to consider them experimental. You can use the command-line option `--python.WarnExperimentalFeatures` to enable warnings for such modules at runtime. In Java embeddings the warnings are enabled by default and you can suppress them by setting the context option 'python.WarnExperimentalFeatures' to 'false'.
1011
* Update to Python 3.11.7
1112
* We now provide intrinsified `_pickle` module also in the community version.
1213
* `polyglot.eval` now raises more meaningful exceptions. Unavaliable languages raise `ValueError`. Exceptions from the polyglot language are raised directly as interop objects (typed as `polyglot.ForeignException`). The shortcut for executing python files without specifying language has been removed, use regular `eval` for executing Python code.

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
GraalPy is a high-performance implementation of the [Python](https://www.python.org/) language for the JVM built on [GraalVM](https://www.graalvm.org/).
88
GraalPy has first-class support for embedding in Java and can turn Python applications into fast, standalone binaries.
9+
GraalPy is ready for production running pure Python code and has experimental support for many popular native extension modules.
910

1011
## Why GraalPy?
1112

@@ -17,15 +18,15 @@ GraalPy has first-class support for embedding in Java and can turn Python applic
1718

1819
**Compatible with the Python ecosystem**
1920

20-
* Install [packages](docs/user/Python-Runtime.md#installing-packages) like *NumPy*, *PyTorch*, or *Tensorflow*; run [Hugging Face](https://huggingface.co/) models like *Stable Diffusion* or *GPT*
21-
* See if the packages you need work with our [Python Compatibility Checker](https://www.graalvm.org/python/compatibility/)
2221
* Use almost any standard Python feature, the CPython tests run on every commit and pass ~85%
2322
![](docs/user/assets/mcd.svg#gh-light-mode-only)![](docs/user/assets/mcd-dark.svg#gh-dark-mode-only)<sup>
2423
We run the tests of the [most depended on PyPI packages](https://libraries.io/pypi) every day.
2524
For 96% of those packages a recent version can be installed on GraalPy and GraalPy passes about 50% of all tests of all packages combined.
2625
We assume that CPython not passing 100% of all tests is due to problems in our infrastructure that may also affect GraalPy.
2726
Packages where CPython fails all tests are marked as "not tested" for both CPython and GraalPy.
2827
</sup>
28+
* See if the packages you need work according to our [Python Compatibility Checker](https://www.graalvm.org/python/compatibility/)
29+
* Support for native extension modules is considered experimental, but you can already install [packages](docs/user/Python-Runtime.md#installing-packages) like *NumPy*, *PyTorch*, or *Tensorflow*; run [Hugging Face](https://huggingface.co/) models like *Stable Diffusion* or *GPT*
2930

3031
**Runs Python code faster**
3132

docs/user/Embedding-Permissions.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ The Java backend is the default when GraalPy is run via the `Context` API, that
5656
GraalPy can log information about known incompatibility of functions executed at runtime, which includes the OS interface-related functions.
5757
To turn on this logging, use the command-line option `--log.python.compatibility.level=FINE` (or other desired logging level).
5858

59-
Known limitations of the of the Java backend are:
59+
Known limitations of the Java backend are:
6060

6161
* Its state is disconnected from the actual OS state, which applies especially to:
6262
* *file descriptors*: Python-level file descriptors are not usable in native code.
@@ -74,3 +74,11 @@ Known limitations of the of the Java backend are:
7474
## Python Native Extensions
7575

7676
Python native extensions run by default as native binaries, with full access to the underlying system.
77+
See [Embedding limitations](Native-Extensions.md#embedding-limitations)
78+
79+
The context permissions needed to run native extensions are:
80+
```java
81+
.allowIO(IOAccess.ALL)
82+
.allowCreateThread(true)
83+
.allowNativeAccess(true)
84+
```

docs/user/Native-Extensions.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
layout: docs-experimental
3+
toc_group: python
4+
link_title: Native Extensions Support
5+
permalink: /reference-manual/python/Native-Extensions/
6+
---
7+
8+
# Native Extensions Support
9+
10+
CPython provides a [native extensions API](https://docs.python.org/3/c-api/index.html){:target="_blank"} for writing Python extensions in C/C++.
11+
GraalPy provides experimental support for this API, which allows many packages like NumPy and PyTorch to work well for many use cases.
12+
The support extends only to the API, not the binary interface (ABI), so extensions built for CPython are not binary compatible with GraalPy.
13+
Packages that use the native API must be built and installed with GraalPy, and the prebuilt wheels for CPython from pypi.org cannot be used.
14+
For best results, it is crucial that you only use the `pip` command that comes preinstalled in GraalPy virtualenvs to install packages.
15+
The version of `pip` shipped with GraalPy applies additional patches to packages upon installation to fix known compatibility issues and it is preconfigured to use an additional repository from graalvm.org where we publish a selection of prebuilt wheels for GraalPy.
16+
Please do not update `pip` or use alternative tools such as `uv`.
17+
18+
## Embedding limitations
19+
20+
Python native extensions run by default as native binaries, with full access to the underlying system.
21+
Native code is not sandboxed and can circumvent any protections Truffle or the JVM may provide, up to and including aborting the entire process.
22+
Native data structures are not subject to the Java GC and the combination of them with Java data structures may lead to memory leaks.
23+
Native libraries generally cannot be loaded multiple times into the same process, and they may contain global state that cannot be safely reset.
24+
Thus, it is not possible to create multiple GraalPy contexts that access native modules within the same JVM.
25+
This includes the case when you create a context, close it, and then create another context.
26+
The second context will not be able to access native extensions.

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ protected void launch(Builder contextBuilder) {
757757
}
758758
contextBuilder.option("python.DontWriteBytecodeFlag", Boolean.toString(dontWriteBytecode));
759759
if (verboseFlag) {
760-
contextBuilder.option("log.python.level", "FINE");
760+
contextBuilder.option("log.python.level", "INFO");
761761
}
762762
contextBuilder.option("python.QuietFlag", Boolean.toString(quietFlag));
763763
contextBuilder.option("python.NoUserSiteFlag", Boolean.toString(noUserSite));
@@ -791,6 +791,10 @@ protected void launch(Builder contextBuilder) {
791791
contextBuilder.option("python.PosixModuleBackend", "java");
792792
}
793793

794+
if (!hasContextOptionSetViaCommandLine("WarnExperimentalFeatures")) {
795+
contextBuilder.option("python.WarnExperimentalFeatures", "false");
796+
}
797+
794798
if (multiContext) {
795799
contextBuilder.engine(Engine.newBuilder().allowExperimentalOptions(true).options(enginePolyglotOptions).build());
796800
}

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_urllib2net.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
*graalpython.lib-python.3.test.test_urllib2net.CloseSocketTest.test_close
22
*graalpython.lib-python.3.test.test_urllib2net.OtherNetworkTests.test_custom_headers
33
*graalpython.lib-python.3.test.test_urllib2net.OtherNetworkTests.test_file
4-
*graalpython.lib-python.3.test.test_urllib2net.OtherNetworkTests.test_ftp
54
*graalpython.lib-python.3.test.test_urllib2net.OtherNetworkTests.test_redirect_url_withfrag
65
*graalpython.lib-python.3.test.test_urllib2net.OtherNetworkTests.test_sites_no_connection_close
76
*graalpython.lib-python.3.test.test_urllib2net.OtherNetworkTests.test_urlwithfrag

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtContext.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353

5454
import java.io.IOException;
5555
import java.nio.file.LinkOption;
56+
import java.util.Set;
57+
import java.util.logging.Level;
5658

5759
import org.graalvm.collections.Pair;
5860
import org.graalvm.shadowed.com.ibm.icu.impl.Punycode;
@@ -274,6 +276,15 @@ private static String dlopenFlagsToString(int flags) {
274276
return str;
275277
}
276278

279+
private static final Set<String> C_EXT_SUPPORTED_LIST = Set.of(
280+
// Stdlib modules are considered supported
281+
"_cpython_sre",
282+
"_cpython_unicodedata",
283+
"_sha3",
284+
"_sqlite3",
285+
"termios",
286+
"pyexpat");
287+
277288
/**
278289
* This method loads a C extension module (C API) and will initialize the corresponding native
279290
* contexts if necessary.
@@ -294,6 +305,17 @@ private static String dlopenFlagsToString(int flags) {
294305
@TruffleBoundary
295306
public static Object loadCExtModule(Node location, PythonContext context, ModuleSpec spec, CheckFunctionResultNode checkFunctionResultNode)
296307
throws IOException, ApiInitException, ImportException {
308+
if (getLogger().isLoggable(Level.WARNING) && context.getOption(PythonOptions.WarnExperimentalFeatures)) {
309+
boolean runViaLauncher = context.getOption(PythonOptions.RunViaLauncher);
310+
if (!runViaLauncher || !C_EXT_SUPPORTED_LIST.contains(spec.name.toJavaStringUncached())) {
311+
String message = "Loading C extension module %s from '%s'. Support for the Python C API is considered experimental.";
312+
if (!runViaLauncher) {
313+
message += " See https://www.graalvm.org/latest/reference-manual/python/Native-Extensions/#embedding-limitations for the limitations. " +
314+
"You can suppress this warning by setting the context option 'python.WarnExperimentalFeatures' to 'false'";
315+
}
316+
getLogger().warning(message.formatted(spec.name, spec.path));
317+
}
318+
}
297319

298320
// we always need to load the CPython C API
299321
CApiContext cApiContext = CApiContext.ensureCapiWasLoaded(location, context, spec.name, spec.path);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ private PythonOptions() {
388388
@EngineOption @Option(category = OptionCategory.INTERNAL, usageSyntax = "true|false", help = "If true, uses native storage strategy for primitive types") //
389389
public static final OptionKey<Boolean> UseNativePrimitiveStorageStrategy = new OptionKey<>(false);
390390

391+
@Option(category = OptionCategory.EXPERT, usageSyntax = "true|false", help = "Print warnings when using experimental features at runtime.", stability = OptionStability.STABLE) //
392+
public static final OptionKey<Boolean> WarnExperimentalFeatures = new OptionKey<>(true);
393+
391394
public static final OptionDescriptors DESCRIPTORS = new PythonOptionsOptionDescriptors();
392395

393396
@CompilationFinal(dimensions = 1) private static final OptionKey<?>[] ENGINE_OPTION_KEYS;

mx.graalpython/mx_graalpython.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,8 @@ def update_unittest_tags(args):
721721
'graalpython.lib-python.3.test.test_buffer.TestBufferProtocol.test_ndarray_slice_multidim',
722722
# Transient failure to delete semaphore on process death
723723
'test.test_multiprocessing_spawn.test_misc.TestResourceTracker.test_resource_tracker_sigkill',
724+
# Connecting to external page that sometimes times out
725+
'graalpython.lib-python.3.test.test_urllib2net.OtherNetworkTests.test_ftp',
724726
]
725727

726728
result_tags = linux_tags & darwin_tags
@@ -1573,7 +1575,7 @@ def graalpython_gate_runner(args, tasks):
15731575
svm_image = python_svm()
15741576
benchmark = os.path.join(PATH_MESO, "image-magix.py")
15751577
out = mx.OutputCapture()
1576-
mx.run([svm_image, "-v", "-S", "--log.python.level=FINEST", benchmark], nonZeroIsFatal=True, out=mx.TeeOutputCapture(out), err=mx.TeeOutputCapture(out))
1578+
mx.run([svm_image, "-S", "--log.python.level=FINE", benchmark], nonZeroIsFatal=True, out=mx.TeeOutputCapture(out), err=mx.TeeOutputCapture(out))
15771579
success = "\n".join([
15781580
"[0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 0, 10, 3, 10, 0, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0]",
15791581
])

0 commit comments

Comments
 (0)