Skip to content

bpo-42888: Remove PyThread_exit_thread() calls from top-level thread … #24241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

izbyshev
Copy link
Contributor

@izbyshev izbyshev commented Jan 18, 2021

…functions

PyThread_exit_thread() uses pthread_exit() on POSIX systems. In glibc,
pthread_exit() is implemented in terms of pthread_cancel(), requiring
the stack unwinder implemented in libgcc. Further, in dynamically
linked applications, calls of pthread_exit() in source code do not
make libgcc_s.so a startup dependency: instead, it's lazily loaded
by glibc via dlopen() when pthread_exit() is called the first time[1].
All of this makes otherwise finely working CPython fail in multithreaded
applications on thread exit if dlopen() fails for any reason.

While providing libgcc_s.so is the reponsibility of the user
(or their package manager), this hidden dependency has been
the source of countless frustrations(e.g. [2]) and, further,
dlopen() may fail for other reasons([3]). But most calls
to PyThread_exit_thread() in CPython are useless because they're done
from the top-level thread function and hence are equivalent
to simply returning. So remove all such calls, thereby avoiding
the glibc cancellation machinery.

The only exception are calls in take_gil() (Python/ceval_gil.h)
which serve as a safety net for daemon threads attempting
to acquire the GIL after Py_Finalize(). Unless a better model for
daemon threads is devised or support for them is removed,
those calls have to be preserved since we need to terminate
the thread right now without touching any interpreter state.

Of course, since PyThread_exit_thread() is a public API,
any extension module can still call it and trip over the same issue.

[1] https://sourceware.org/legacy-ml/libc-help/2014-07/msg00000.html
[2] https://stackoverflow.com/questions/64797838/libgcc-s-so-1-must-be-installed-for-pthread-cancel-to-work
[3] https://www.sourceware.org/bugzilla/show_bug.cgi?id=13119

https://bugs.python.org/issue42888

…functions

PyThread_exit_thread() uses pthread_exit() on POSIX systems. In glibc,
pthread_exit() is implemented in terms of pthread_cancel(), requiring
the stack unwinder implemented in libgcc. Further, in dynamically
linked applications, calls of pthread_exit() in source code do not
make libgcc_s.so a startup dependency: instead, it's lazily loaded
by glibc via dlopen() when pthread_exit() is called the first time[1].
All of this makes otherwise finely working CPython fail in multithreaded
applications on thread exit if dlopen() fails for any reason.

While providing libgcc_s.so is the reponsibility of the user
(or their package manager), this hidden dependency has been
the source of countless frustrations(e.g. [2]) and, further,
dlopen() may fail for other reasons([3]). But most calls
to PyThread_exit_thread() in CPython are useless because they're done
from the top-level thread function and hence are equivalent
to simply returning. So remove all such calls, thereby avoiding
the glibc cancellation machinery.

The only exception are calls in take_gil() (Python/ceval_gil.h)
which serve as a safety net for daemon threads attempting
to acquire the GIL after Py_Finalize(). Unless a better model for
daemon threads is devised or support for them is removed,
those calls have to be preserved since we need to terminate
the thread right now without touching any interpreter state.

Of course, since PyThread_exit_thread() is a public API,
any extension module can still call it and trip over the same issue.

[1] https://sourceware.org/legacy-ml/libc-help/2014-07/msg00000.html
[2] https://stackoverflow.com/questions/64797838/libgcc-s-so-1-must-be-installed-for-pthread-cancel-to-work
[3] https://www.sourceware.org/bugzilla/show_bug.cgi?id=13119
@github-actions
Copy link

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale Stale PR or inactive for long period of time. label Feb 21, 2021
@vstinner
Copy link
Member

Oh, I missed this PR. I merged PR #26758 which does basically the same.

I missed the two calls to PyThread_exit_thread() in tests: I extracted these changes in PR #26943.

I'm not sure if your changes in logs (_pythread_at_thread_exit() function) are useful, so I ignored this part.

@vstinner vstinner closed this Jun 28, 2021
@izbyshev
Copy link
Contributor Author

izbyshev commented Jul 7, 2021

I'm not sure if your changes in logs (_pythread_at_thread_exit() function) are useful, so I ignored this part.

Those changes preserve the debug output (dprintf() call) on thread exit which is lost after removal of PyThread_exit_thread() calls. I don't know how useful this debug output is in practice; my only intention was to preserve the existing behavior.

@vstinner
Copy link
Member

vstinner commented Jul 7, 2021

I never ever used PYTHONTHREADDEBUG=1. I just tried:

$ PYTHONTHREADDEBUG=1 ./python -m test -v test_sys
PyThread_init_thread called
PyThread_allocate_lock() -> 0x1f212c0
PyThread_allocate_lock called
PyThread_allocate_lock() -> 0x1f21300
PyThread_allocate_lock called
PyThread_allocate_lock() -> 0x1f21340
PyThread_allocate_lock called
PyThread_allocate_lock() -> 0x1f3ea70
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) -> 1
PyThread_release_lock(0x1f212c0) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) -> 1
PyThread_release_lock(0x1f212c0) called
PyThread_acquire_lock_timed(0x1f21340, -1, 0) called
PyThread_acquire_lock_timed(0x1f21340, -1, 0) -> 1
PyThread_release_lock(0x1f21340) called
PyThread_acquire_lock_timed(0x1f21340, -1, 0) called
PyThread_acquire_lock_timed(0x1f21340, -1, 0) -> 1
PyThread_release_lock(0x1f21340) called
PyThread_acquire_lock_timed(0x1f21340, -1, 0) called
PyThread_acquire_lock_timed(0x1f21340, -1, 0) -> 1
PyThread_release_lock(0x1f21340) called
PyThread_acquire_lock_timed(0x1f21340, -1, 0) called
PyThread_acquire_lock_timed(0x1f21340, -1, 0) -> 1
PyThread_release_lock(0x1f21340) called
(...)
======================================================================
FAIL: test_changing_sys_stderr_and_removing_reference (test.test_sys.SizeofTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/vstinner/python/main/Lib/test/test_sys.py", line 1525, in test_changing_sys_stderr_and_removing_reference
    self.assertEqual(out, b"")
     ^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: b'PyThread_init_thread called\nPyThread_all[59813 chars]ed\n' != b''
(...)
PyThread_release_lock(0x2113660) called
PyThread_free_lock(0x2113660) called
PyThread_free_lock(0x7fc6e8001300) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) -> 1
PyThread_release_lock(0x1f212c0) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) -> 1
PyThread_release_lock(0x1f212c0) called
PyThread_free_lock(0x1f3ea70) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) called
PyThread_acquire_lock_timed(0x1f212c0, -1, 0) -> 1
PyThread_release_lock(0x1f212c0) called
PyThread_free_lock(0x1f212c0) called
PyThread_free_lock(0x1f21300) called
PyThread_free_lock(0x1f21340) called

This debug mode produces so many logs that it looks basically useless :-(

@vstinner
Copy link
Member

vstinner commented Jul 8, 2021

I started "Does anyone use threading debug PYTHONTHREADDEBUG=1 env var? Can I remove it?" thread on python-dev:
https://mail.python.org/archives/list/[email protected]/thread/NMLGCDRUKLZSTK4UICJTKR54WRXU2ZGJ/

@vstinner
Copy link
Member

vstinner commented Jul 8, 2021

I created https://bugs.python.org/issue44584 "Deprecate thread debugging PYTHONTHREADDEBUG=1".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting review stale Stale PR or inactive for long period of time.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants