This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Race condition in `threadig.Thread._wait_for_tstate_lock`
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.11, Python 3.10, Python 3.9, Python 3.8, Python 3.7
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: dmaurer
Priority: normal Keywords:

Created on 2022-04-07 08:44 by dmaurer, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (3)
msg416919 - (view) Author: Dieter Maurer (dmaurer) Date: 2022-04-07 08:44
I have observed an `AssertionError (assert self._is_stopped)` in `threading.Thread._wait_for_tstate_lock`. This indicates that Python's internal state has been corrupted.

The analysis revealed the following race condition:
`_wait_for_tstate:lock` contains the code:
```
                lock.release()
                self._stop()
```
The `lock.release()` allows a conflicting call to execute. If this happens before `self._stop()` has executed `self._is_stopped = True`, the `AssertionError` is raised.

I suggest to give `_stop` an additional parameter `locked` with default `False`. In indicates whether the caller holds the `tstate_lock`. If this is the case, `_stop` releases the lock after it has ensured a consistent state (esspecially set `_is_stopped` to `True`). With this modification to `_stop` the two lines above can be replaced by `self._stop(locked=True)`.
msg416920 - (view) Author: Dieter Maurer (dmaurer) Date: 2022-04-07 09:34
Apparently, the explanation is not that easy: `_stop` first sets `_is_stopped` to `True` and only then `_tstate_lock` to `None`. Therefore, the race should not cause the `AssertionError`.

I observed the `AssertionError` in Python 3.6. The related `threading` code is however almost identical to that in Python 3.11.
msg416922 - (view) Author: Dieter Maurer (dmaurer) Date: 2022-04-07 09:53
The observation was caused by a bug which has been fixed in newer Python versions (3.9+ if I remember correctly). `isAlive` was called on a `_DummyThread` (while `_DummyThread` overides `is_alive` it had forgotten to override `isAlive` as well).
History
Date User Action Args
2022-04-11 14:59:58adminsetgithub: 91402
2022-04-07 09:53:50dmaurersetstatus: open -> closed
resolution: rejected
messages: + msg416922

stage: resolved
2022-04-07 09:34:45dmaurersetmessages: + msg416920
2022-04-07 08:44:47dmaurercreate