This is an archive of the discontinued LLVM Phabricator instance.

[lldb] Remove nothreadallow from SWIG's __str__ wrappers to work around a Python>=3.7 crash
ClosedPublic

Authored by teemperor on Sep 25 2020, 7:21 AM.

Details

Summary

Usually when we enter a SWIG wrapper function from Python, SWIG automatically adds a
Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS around the call
to the SB API C++ function. This will ensure that Python's GIL is released
when we enter LLDB and locked again when we return to the wrapper code.

D51569 changed this behaviour but only for the generated __str__ wrappers. The added
nothreadallow disables the injection of the GIL release/re-acquire code and the
GIL is now kept locked when entering LLDB and is expected to be still locked when
returning from the LLDB implementation. The main reason for that was that back when
D51569 landed the wrapper itself created a Python string. These days it just creates
a std::string and SWIG itself takes care of getting the GIL and creating the Python string
from the std::string, so that workaround isn't necessary anymore.

This patch just removes nothreadallow so that our __str__ functions now behave like
all other wrapper functions in that they release the GIL when calling into the SB API implementation.

The motivation here is actually to work around another potential bug in LLDB. When one
calls into the LLDB SB API while holding the GIL and that call causes LLDB to interpret
some Python script via ScriptInterpreterPython, then the GIL will be unlocked when the
control flow returns from the SB API. In the case of the __str__ wrapper this would cause
that the next call to a Python function requiring the GIL would fail (as SWIG will not try to
reacquire the GIL as it isn't aware that LLDB removed it).

The reason for this unexpected GIL release seems to be a workaround for recent Python versions:

#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7) || (PY_MAJOR_VERSION > 3)  
    // The only case we should go further and acquire the GIL: it is unlocked.  
    if (PyGILState_Check())                                                     
      return;                                                                   
#endif

The early-exit here causes InitializePythonRAII::m_was_already_initialized to be always
false and that causes that InitializePythonRAII's destructor always directly unlocks the
GIL via PyEval_SaveThread. I'm investigating how to properly fix this bug in a follow up patch, but
for now this straightforward patch seems to be enough to unblock my other patches (and it
also has the benefit of removing this workaround).

The test for this is just a simple test for std::deque which has a synthetic child provider
implemented as a Python script. Inspecting the deque object will cause expect_expr to
create a string error message by calling str(deque_object). Printing the ValueObject causes
the Python script for the synthetic children to execute which then triggers the bug
described above where the GIL ends up being unlocked.

Diff Detail

Event Timeline

teemperor requested review of this revision.Sep 25 2020, 7:21 AM
teemperor created this revision.

If anyone knows another SB object where calling str on it would execute a Python script (and that is simpler than an synthetic child provider), then let me know.

JDevlieghere accepted this revision.Sep 25 2020, 8:18 AM

Thanks for the thorough explanation. LGTM.

This revision is now accepted and ready to land.Sep 25 2020, 8:18 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 28 2020, 1:11 AM