Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ language runtime. The main focus is on user-observable behavior of the engine.
* Added support for specifying generics on foreign classes, and inheriting from such classes. Especially when using Java classes that support generics, this allows expressing the generic types in Python type annotations as well.
* Added a new `java` backend for the `pyexpat` module that uses a Java XML parser instead of the native `expat` library. It can be useful when running without native access or multiple-context scenarios. This backend is the default when embedding and can be switched back to native `expat` by setting `python.PyExpatModuleBackend` option to `native`. Standalone distribution still defaults to native expat backend.
* Add a new context option `python.UnicodeCharacterDatabaseNativeFallback` to control whether the ICU database may fall back to the native unicode character database from CPython for features and characters not supported by ICU. This requires native access to be enabled and is disabled by default for embeddings.
* Add a new context option `python.AllowSignalHandlers` to control whether Python code may install signal handlers. This is disabled by default for Java embedding and enabled in the standalone.
* Add an experimental `python.InitializationEntropySource` option to control the entropy source used for initialization-only randomness such as hash secret generation and `random.Random(None)` seeding. This means embeddings and tests can select deterministic or externally provided initialization entropy without affecting cryptographically relevant APIs like `os.urandom()` or `random.SystemRandom()`.
* Foreign temporal objects (dates, times, and timezones) are now given a Python class corresponding to their interop traits, i.e., `date`, `time`, `datetime`, or `tzinfo`. This allows any foreign objects with these traits to be used in place of the native Python types and Python methods available on these types work on the foreign types.
* Make BouncyCastle an optional dependency for embedding use cases. BouncyCastle is only needed for legacy RSA, DSA, and EC privat keys versions 0 and 1. To support these from Python embeddings, BouncyCastle must now be explicitly enabled by adding the `org.graalvm.python:python-bouncycastle-support` Maven artifact.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,8 @@ protected void launch(Builder contextBuilder) {
contextBuilder.option("python.PyCachePrefix", cachePrefix);
}

setOptionIfNotSetViaCommandLine(contextBuilder, "AllowSignalHandlers", "true");

if (IS_WINDOWS) {
contextBuilder.option("python.PosixModuleBackend", "java");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -44,6 +44,7 @@
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -77,7 +78,7 @@ Stream<Thread> pythonThreads() {
public void testNoNewThreadsWithoutAutomaticAsyncActions() {
Assume.assumeTrue("false".equalsIgnoreCase(System.getProperty("python.AutomaticAsyncActions")));
long threadCount = pythonThreadCount();
Context c = PythonTests.enterContext();
Context c = PythonTests.enterContext(Map.of("python.AllowSignalHandlers", "true", "python.PosixModuleBackend", "native"), new String[0]);
try {
Runnable poll = c.getPolyglotBindings().getMember("PollPythonAsyncActions").asHostObject();
c.eval("python", "import re, itertools, functools, _testcapi");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -82,6 +82,84 @@ def handler(signum, frame):
assert False, "Signal handler didn't trigger or propagate exception"


def test_itimer():
assert signal.getitimer(signal.ITIMER_REAL) == (0.0, 0.0)
try:
old = signal.setitimer(signal.ITIMER_REAL, 1.0, 0.25)
assert old == (0.0, 0.0), old
current = signal.getitimer(signal.ITIMER_REAL)
assert 0.0 <= current[0] <= 1.0, current
assert current[1] == 0.25, current
old = signal.setitimer(signal.ITIMER_REAL, 0)
assert 0.0 <= old[0] <= 1.0, old
assert old[1] == 0.25, old
finally:
signal.setitimer(signal.ITIMER_REAL, 0)

for func, args in (
(signal.getitimer, (-1,)),
(signal.setitimer, (-1, 0)),
(signal.setitimer, (signal.ITIMER_REAL, -1)),
):
try:
func(*args)
except signal.ItimerError as e:
assert e.errno == 22, e
else:
raise AssertionError(f"{func.__name__}{args} did not raise ItimerError")


def test_emulated_timers_use_current_handler():
if sys.implementation.name != 'graalpy' or __graalpython__.posix_module_backend() != 'java':
return

import time

calls = []

def first(signum, frame):
calls.append(("first", signum, frame))

def second(signum, frame):
calls.append(("second", signum, frame))

def wait_for_call(timeout=2.5):
deadline = time.time() + timeout
while time.time() < deadline:
if calls:
return True
time.sleep(0.01)
return False

old_handler = signal.signal(signal.SIGALRM, first)
try:
signal.alarm(1)
signal.signal(signal.SIGALRM, second)
assert wait_for_call(), "alarm did not trigger handler"
assert calls[0][0] == "second", calls
assert calls[0][1] == signal.SIGALRM, calls
assert calls[0][2].f_code.co_name

calls.clear()
signal.signal(signal.SIGALRM, first)
signal.setitimer(signal.ITIMER_REAL, 0.05)
signal.signal(signal.SIGALRM, second)
assert wait_for_call(), "setitimer did not trigger handler"
assert calls[0][0] == "second", calls
assert calls[0][1] == signal.SIGALRM, calls
assert calls[0][2].f_code.co_name

calls.clear()
signal.signal(signal.SIGALRM, signal.SIG_IGN)
signal.setitimer(signal.ITIMER_REAL, 0.05)
time.sleep(0.2)
assert calls == []
finally:
signal.alarm(0)
signal.setitimer(signal.ITIMER_REAL, 0)
signal.signal(signal.SIGALRM, old_handler)


def test_interrupt():
if sys.implementation.name == 'graalpy' and __graalpython__.posix_module_backend() == 'java':
# Sending SIGINT does not work when using the Java backend for posix
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import static com.oracle.graal.python.nodes.BuiltinNames.J_POLYGLOT;
import static com.oracle.graal.python.nodes.BuiltinNames.J_POSIX;
import static com.oracle.graal.python.nodes.BuiltinNames.J_PROPERTY;
import static com.oracle.graal.python.nodes.BuiltinNames.J_SIGNAL;
import static com.oracle.graal.python.nodes.BuiltinNames.J_SHA1;
import static com.oracle.graal.python.nodes.BuiltinNames.J_SHA2;
import static com.oracle.graal.python.nodes.BuiltinNames.J_SHA3;
Expand All @@ -63,6 +64,7 @@
import static com.oracle.graal.python.nodes.BuiltinNames.J_TYPING;
import static com.oracle.graal.python.nodes.BuiltinNames.J_WRAPPER_DESCRIPTOR;
import static com.oracle.graal.python.nodes.BuiltinNames.J__CONTEXTVARS;
import static com.oracle.graal.python.nodes.BuiltinNames.J__SIGNAL;
import static com.oracle.graal.python.nodes.BuiltinNames.J__SOCKET;
import static com.oracle.graal.python.nodes.BuiltinNames.J__SSL;
import static com.oracle.graal.python.nodes.BuiltinNames.J__STRUCT;
Expand Down Expand Up @@ -785,6 +787,7 @@ It can be called either on the class (e.g. C.f()) or on an instance
PickleError("PickleError", Exception, newBuilder().publishInModule("_pickle").basetype().addDict()),
PicklingError("PicklingError", PickleError, newBuilder().publishInModule("_pickle").basetype().addDict()),
UnpicklingError("UnpicklingError", PickleError, newBuilder().publishInModule("_pickle").basetype().addDict()),
SignalItimerError("itimer_error", OSError, newBuilder().publishInModule(J__SIGNAL).moduleName(J_SIGNAL).basetype().addDict()),
SocketGAIError("gaierror", OSError, newBuilder().publishInModule(J__SOCKET).basetype().addDict()),
SocketHError("herror", OSError, newBuilder().publishInModule(J__SOCKET).basetype().addDict()),
BinasciiError("Error", ValueError, newBuilder().publishInModule("binascii").basetype().addDict()),
Expand Down
Loading
Loading