33#include <jni.h>
44#include <pthread.h>
55#include <Python.h>
6+ #include <signal.h>
67#include <stdio.h>
78#include <string.h>
89#include <unistd.h>
@@ -15,6 +16,13 @@ static void throw_runtime_exception(JNIEnv *env, const char *message) {
1516 message );
1617}
1718
19+ static void throw_errno (JNIEnv * env , const char * error_prefix ) {
20+ char error_message [1024 ];
21+ snprintf (error_message , sizeof (error_message ),
22+ "%s: %s" , error_prefix , strerror (errno ));
23+ throw_runtime_exception (env , error_message );
24+ }
25+
1826
1927// --- Stdio redirection ------------------------------------------------------
2028
@@ -95,10 +103,7 @@ JNIEXPORT void JNICALL Java_org_python_testbed_PythonTestRunner_redirectStdioToL
95103 for (StreamInfo * si = STREAMS ; si -> file ; si ++ ) {
96104 char * error_prefix ;
97105 if ((error_prefix = redirect_stream (si ))) {
98- char error_message [1024 ];
99- snprintf (error_message , sizeof (error_message ),
100- "%s: %s" , error_prefix , strerror (errno ));
101- throw_runtime_exception (env , error_message );
106+ throw_errno (env , error_prefix );
102107 return ;
103108 }
104109 }
@@ -107,41 +112,86 @@ JNIEXPORT void JNICALL Java_org_python_testbed_PythonTestRunner_redirectStdioToL
107112
108113// --- Python initialization ---------------------------------------------------
109114
110- static PyStatus set_config_string (
111- JNIEnv * env , PyConfig * config , wchar_t * * config_str , jstring value
112- ) {
113- const char * value_utf8 = (* env )-> GetStringUTFChars (env , value , NULL );
114- PyStatus status = PyConfig_SetBytesString (config , config_str , value_utf8 );
115- (* env )-> ReleaseStringUTFChars (env , value , value_utf8 );
116- return status ;
115+ static char * init_signals () {
116+ // Some tests use SIGUSR1, but that's blocked by default in an Android app in
117+ // order to make it available to `sigwait` in the Signal Catcher thread.
118+ // (https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:art/runtime/signal_catcher.cc).
119+ // That thread's functionality is only useful for debugging the JVM, so disabling
120+ // it should not weaken the tests.
121+ //
122+ // There's no safe way of stopping the thread completely (#123982), but simply
123+ // unblocking SIGUSR1 is enough to fix most tests.
124+ //
125+ // However, in tests that generate multiple different signals in quick
126+ // succession, it's possible for SIGUSR1 to arrive while the main thread is busy
127+ // running the C-level handler for a different signal. In that case, the SIGUSR1
128+ // may be sent to the Signal Catcher thread instead, which will generate a log
129+ // message containing the text "reacting to signal".
130+ //
131+ // Such tests may need to be changed in one of the following ways:
132+ // * Use a signal other than SIGUSR1 (e.g. test_stress_delivery_simultaneous in
133+ // test_signal.py).
134+ // * Send the signal to a specific thread rather than the whole process (e.g.
135+ // test_signals in test_threadsignals.py.
136+ sigset_t set ;
137+ if (sigemptyset (& set )) {
138+ return "sigemptyset" ;
139+ }
140+ if (sigaddset (& set , SIGUSR1 )) {
141+ return "sigaddset" ;
142+ }
143+ if ((errno = pthread_sigmask (SIG_UNBLOCK , & set , NULL ))) {
144+ return "pthread_sigmask" ;
145+ }
146+ return NULL ;
117147}
118148
119149static void throw_status (JNIEnv * env , PyStatus status ) {
120150 throw_runtime_exception (env , status .err_msg ? status .err_msg : "" );
121151}
122152
123153JNIEXPORT int JNICALL Java_org_python_testbed_PythonTestRunner_runPython (
124- JNIEnv * env , jobject obj , jstring home , jstring runModule
154+ JNIEnv * env , jobject obj , jstring home , jarray args
125155) {
156+ const char * home_utf8 = (* env )-> GetStringUTFChars (env , home , NULL );
157+ char cwd [PATH_MAX ];
158+ snprintf (cwd , sizeof (cwd ), "%s/%s" , home_utf8 , "cwd" );
159+ if (chdir (cwd )) {
160+ throw_errno (env , "chdir" );
161+ return 1 ;
162+ }
163+
164+ char * error_prefix ;
165+ if ((error_prefix = init_signals ())) {
166+ throw_errno (env , error_prefix );
167+ return 1 ;
168+ }
169+
126170 PyConfig config ;
127171 PyStatus status ;
128- PyConfig_InitIsolatedConfig (& config );
172+ PyConfig_InitPythonConfig (& config );
129173
130- status = set_config_string (env , & config , & config .home , home );
131- if (PyStatus_Exception (status )) {
174+ jsize argc = (* env )-> GetArrayLength (env , args );
175+ const char * argv [argc + 1 ];
176+ for (int i = 0 ; i < argc ; i ++ ) {
177+ jobject arg = (* env )-> GetObjectArrayElement (env , args , i );
178+ argv [i ] = (* env )-> GetStringUTFChars (env , arg , NULL );
179+ }
180+ argv [argc ] = NULL ;
181+
182+ // PyConfig_SetBytesArgv "must be called before other methods, since the
183+ // preinitialization configuration depends on command line arguments"
184+ if (PyStatus_Exception (status = PyConfig_SetBytesArgv (& config , argc , (char * * )argv ))) {
132185 throw_status (env , status );
133186 return 1 ;
134187 }
135188
136- status = set_config_string ( env , & config , & config .run_module , runModule );
189+ status = PyConfig_SetBytesString ( & config , & config .home , home_utf8 );
137190 if (PyStatus_Exception (status )) {
138191 throw_status (env , status );
139192 return 1 ;
140193 }
141194
142- // Some tests generate SIGPIPE and SIGXFSZ, which should be ignored.
143- config .install_signal_handlers = 1 ;
144-
145195 status = Py_InitializeFromConfig (& config );
146196 if (PyStatus_Exception (status )) {
147197 throw_status (env , status );
0 commit comments