diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2017-11-30 22:36:49 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-30 22:36:49 (GMT) |
commit | e10c9de9d74fd4c26b32e6719d96f04a5be6987d (patch) | |
tree | d52af6d26b53ff529cfab22ea50bb6b5faea7a03 /Programs | |
parent | 29cb50ba347d9dc18e0720bef8e9caedd012a3cd (diff) | |
download | cpython-e10c9de9d74fd4c26b32e6719d96f04a5be6987d.zip cpython-e10c9de9d74fd4c26b32e6719d96f04a5be6987d.tar.gz cpython-e10c9de9d74fd4c26b32e6719d96f04a5be6987d.tar.bz2 |
bpo-20891: Fix PyGILState_Ensure() (#4650) (#4655)
When PyGILState_Ensure() is called in a non-Python thread before
PyEval_InitThreads(), only call PyEval_InitThreads() after calling
PyThreadState_New() to fix a crash.
Add an unit test in test_embed.
Enhance also embedded tests, backport from master:
* Add test_pre_initialization_api()
* Set PYTHONIOENCODING environment variable in
test_forced_io_encoding()
(cherry picked from commit b4d1e1f7c1af6ae33f0e371576c8bcafedb099db)
Diffstat (limited to 'Programs')
-rw-r--r-- | Programs/_testembed.c | 128 |
1 files changed, 117 insertions, 11 deletions
diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 3968399..b0f9087 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -1,4 +1,5 @@ #include <Python.h> +#include "pythread.h" #include <stdio.h> /********************************************************* @@ -33,7 +34,7 @@ static void print_subinterp(void) ); } -static void test_repeated_init_and_subinterpreters(void) +static int test_repeated_init_and_subinterpreters(void) { PyThreadState *mainstate, *substate; #ifdef WITH_THREAD @@ -70,6 +71,7 @@ static void test_repeated_init_and_subinterpreters(void) PyEval_RestoreThread(mainstate); Py_Finalize(); } + return 0; } /***************************************************** @@ -103,7 +105,7 @@ static void check_stdio_details(const char *encoding, const char * errors) Py_Finalize(); } -static void test_forced_io_encoding(void) +static int test_forced_io_encoding(void) { /* Check various combinations */ printf("--- Use defaults ---\n"); @@ -122,19 +124,123 @@ static void test_forced_io_encoding(void) printf("Unexpected success calling Py_SetStandardStreamEncoding"); } Py_Finalize(); + return 0; } -/* Different embedding tests */ -int main(int argc, char *argv[]) + +/********************************************************* + * Test parts of the C-API that work before initialization + *********************************************************/ + +static int test_pre_initialization_api(void) { + /* Leading "./" ensures getpath.c can still find the standard library */ + wchar_t *program = Py_DecodeLocale("./spam", NULL); + if (program == NULL) { + fprintf(stderr, "Fatal error: cannot decode program name\n"); + return 1; + } + Py_SetProgramName(program); - /* TODO: Check the argument string to allow for more test cases */ - if (argc > 1) { - /* For now: assume "forced_io_encoding */ - test_forced_io_encoding(); - } else { - /* Run the original embedding test case by default */ - test_repeated_init_and_subinterpreters(); + Py_Initialize(); + Py_Finalize(); + + PyMem_RawFree(program); + return 0; +} + +static void bpo20891_thread(void *lockp) +{ + PyThread_type_lock lock = *((PyThread_type_lock*)lockp); + + PyGILState_STATE state = PyGILState_Ensure(); + if (!PyGILState_Check()) { + fprintf(stderr, "PyGILState_Check failed!"); + abort(); + } + + PyGILState_Release(state); + + PyThread_release_lock(lock); + + PyThread_exit_thread(); +} + +static int test_bpo20891(void) +{ + /* bpo-20891: Calling PyGILState_Ensure in a non-Python thread before + calling PyEval_InitThreads() must not crash. PyGILState_Ensure() must + call PyEval_InitThreads() for us in this case. */ + PyThread_type_lock lock = PyThread_allocate_lock(); + if (!lock) { + fprintf(stderr, "PyThread_allocate_lock failed!"); + return 1; + } + + _testembed_Py_Initialize(); + + long thrd = PyThread_start_new_thread(bpo20891_thread, &lock); + if (thrd == -1) { + fprintf(stderr, "PyThread_start_new_thread failed!"); + return 1; } + PyThread_acquire_lock(lock, WAIT_LOCK); + + Py_BEGIN_ALLOW_THREADS + /* wait until the thread exit */ + PyThread_acquire_lock(lock, WAIT_LOCK); + Py_END_ALLOW_THREADS + + PyThread_free_lock(lock); + return 0; } + + +/* ********************************************************* + * List of test cases and the function that implements it. + * + * Names are compared case-sensitively with the first + * argument. If no match is found, or no first argument was + * provided, the names of all test cases are printed and + * the exit code will be -1. + * + * The int returned from test functions is used as the exit + * code, and test_capi treats all non-zero exit codes as a + * failed test. + *********************************************************/ +struct TestCase +{ + const char *name; + int (*func)(void); +}; + +static struct TestCase TestCases[] = { + { "forced_io_encoding", test_forced_io_encoding }, + { "repeated_init_and_subinterpreters", test_repeated_init_and_subinterpreters }, + { "pre_initialization_api", test_pre_initialization_api }, + { "bpo20891", test_bpo20891 }, + { NULL, NULL } +}; + +int main(int argc, char *argv[]) +{ + if (argc > 1) { + for (struct TestCase *tc = TestCases; tc && tc->name; tc++) { + if (strcmp(argv[1], tc->name) == 0) + return (*tc->func)(); + } + } + + /* No match found, or no test name provided, so display usage */ + printf("Python " PY_VERSION " _testembed executable for embedded interpreter tests\n" + "Normally executed via 'EmbeddingTests' in Lib/test/test_capi.py\n\n" + "Usage: %s TESTNAME\n\nAll available tests:\n", argv[0]); + for (struct TestCase *tc = TestCases; tc && tc->name; tc++) { + printf(" %s\n", tc->name); + } + + /* Non-zero exit code will cause test_capi.py tests to fail. + This is intentional. */ + return -1; +} |