Fix Python initialization for Python 3.

Python 3 reverses the order in which you must call Py_InitializeEx
and PyEval_InitThreads.  Since that log is in itself already a
little nuanced, it is refactored into a function so that the reversal
is more clear.  At the same time, there's a lot of logic during
Python initialization to save off a bunch of state and then restore
it after initialization is complete.  To express this more cleanly,
it is refactored to an RAII-style pattern where state is saved off
on acquisition and restored on release.

llvm-svn: 250306
This commit is contained in:
Zachary Turner 2015-10-14 17:51:29 +00:00
parent 4bca0bb010
commit 079fe48a1d
1 changed files with 93 additions and 48 deletions

View File

@ -80,27 +80,101 @@ static ScriptInterpreterPython::SWIGPythonCallThreadPlan g_swig_call_thread_plan
static bool g_initialized = false; static bool g_initialized = false;
#if PY_MAJOR_VERSION >= 3 && defined(LLDB_PYTHON_HOME) namespace
typedef wchar_t PythonHomeChar;
#else
typedef char PythonHomeChar;
#endif
PythonHomeChar *
GetDesiredPythonHome()
{ {
// Initializing Python is not a straightforward process. We cannot control what
// external code may have done before getting to this point in LLDB, including
// potentially having already initialized Python, so we need to do a lot of work
// to ensure that the existing state of the system is maintained across our
// initialization. We do this by using an RAII pattern where we save off initial
// state at the beginning, and restore it at the end
struct InitializePythonRAII
{
public:
InitializePythonRAII() :
m_was_already_initialized(false),
m_gil_state(PyGILState_UNLOCKED)
{
// Python will muck with STDIN terminal state, so save off any current TTY
// settings so we can restore them.
m_stdin_tty_state.Save(STDIN_FILENO, false);
InitializePythonHome();
// Python < 3.2 and Python >= 3.2 reversed the ordering requirements for
// calling `Py_Initialize` and `PyEval_InitThreads`. < 3.2 requires that you
// call `PyEval_InitThreads` first, and >= 3.2 requires that you call it last.
#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2) || (PY_MAJOR_VERSION > 3)
Py_InitializeEx(0);
InitializeThreadsPrivate();
#else
InitializeThreadsPrivate();
Py_InitializeEx(0);
#endif
}
~InitializePythonRAII()
{
if (m_was_already_initialized)
{
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_SCRIPT | LIBLLDB_LOG_VERBOSE));
if (log)
{
log->Printf("Releasing PyGILState. Returning to state = %slocked\n",
m_was_already_initialized == PyGILState_UNLOCKED ? "un" : "");
}
PyGILState_Release(m_gil_state);
}
else
{
// We initialized the threads in this function, just unlock the GIL.
PyEval_SaveThread();
}
m_stdin_tty_state.Restore();
}
private:
void InitializePythonHome()
{
#if defined(LLDB_PYTHON_HOME) #if defined(LLDB_PYTHON_HOME)
#if PY_MAJOR_VERSION >= 3 #if PY_MAJOR_VERSION >= 3
size_t size = 0; size_t size = 0;
static PythonHomeChar *g_python_home = Py_DecodeLocale(LLDB_PYTHON_HOME, &size); static wchar_t *g_python_home = Py_DecodeLocale(LLDB_PYTHON_HOME, &size);
return g_python_home;
#else #else
static PythonHomeChar *g_python_home = LLDB_PYTHON_HOME; static char *g_python_home = LLDB_PYTHON_HOME;
return g_python_home;
#endif #endif
#else Py_SetPythonHome(g_python_home);
return nullptr;
#endif #endif
}
void InitializeThreadsPrivate()
{
if (PyEval_ThreadsInitialized())
{
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_SCRIPT | LIBLLDB_LOG_VERBOSE));
m_was_already_initialized = true;
m_gil_state = PyGILState_Ensure();
if (log)
{
log->Printf("Ensured PyGILState. Previous state = %slocked\n",
m_gil_state == PyGILState_UNLOCKED ? "un" : "");
}
return;
}
// InitThreads acquires the GIL if it hasn't been called before.
PyEval_InitThreads();
}
TerminalState m_stdin_tty_state;
PyGILState_STATE m_gil_state;
bool m_was_already_initialized;
};
} }
static std::string ReadPythonBacktrace(PythonObject py_backtrace); static std::string ReadPythonBacktrace(PythonObject py_backtrace);
@ -3104,28 +3178,10 @@ ScriptInterpreterPython::InitializePrivate ()
Timer scoped_timer (__PRETTY_FUNCTION__, __PRETTY_FUNCTION__); Timer scoped_timer (__PRETTY_FUNCTION__, __PRETTY_FUNCTION__);
// Python will muck with STDIN terminal state, so save off any current TTY // RAII-based initialization which correctly handles multiple-initialization, version-
// settings so we can restore them. // specific differences among Python 2 and Python 3, and saving and restoring various
TerminalState stdin_tty_state; // other pieces of state that can get mucked with during initialization.
stdin_tty_state.Save(STDIN_FILENO, false); InitializePythonRAII initialize_guard;
#if defined(LLDB_PYTHON_HOME)
Py_SetPythonHome(GetDesiredPythonHome());
#endif
PyGILState_STATE gstate;
Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_SCRIPT | LIBLLDB_LOG_VERBOSE));
bool threads_already_initialized = false;
if (PyEval_ThreadsInitialized ()) {
gstate = PyGILState_Ensure ();
if (log)
log->Printf("Ensured PyGILState. Previous state = %slocked\n", gstate == PyGILState_UNLOCKED ? "un" : "");
threads_already_initialized = true;
} else {
// InitThreads acquires the GIL if it hasn't been called before.
PyEval_InitThreads ();
}
Py_InitializeEx (0);
if (g_swig_init_callback) if (g_swig_init_callback)
g_swig_init_callback (); g_swig_init_callback ();
@ -3146,17 +3202,6 @@ ScriptInterpreterPython::InitializePrivate ()
AddToSysPath(AddLocation::Beginning, file_spec.GetPath(false)); AddToSysPath(AddLocation::Beginning, file_spec.GetPath(false));
PyRun_SimpleString ("sys.dont_write_bytecode = 1; import lldb.embedded_interpreter; from lldb.embedded_interpreter import run_python_interpreter; from lldb.embedded_interpreter import run_one_line"); PyRun_SimpleString ("sys.dont_write_bytecode = 1; import lldb.embedded_interpreter; from lldb.embedded_interpreter import run_python_interpreter; from lldb.embedded_interpreter import run_one_line");
if (threads_already_initialized) {
if (log)
log->Printf("Releasing PyGILState. Returning to state = %slocked\n", gstate == PyGILState_UNLOCKED ? "un" : "");
PyGILState_Release (gstate);
} else {
// We initialized the threads in this function, just unlock the GIL.
PyEval_SaveThread();
}
stdin_tty_state.Restore();
} }
void void