Add thread-safe context manager to "warnings" module #128300
Draft
+397
−58
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The existing context manager
warning.catch_warnings()
is inherently thread-unsafe. Here is some example code that can show the issue:Since
test_warning()
is running in separate threads, there is no consistent ordering of which context manager exits first. That means thefilters
value that gets restored is also not consistent (a race between the threads about which version of the filters value they will restore).This PR adds a new thread-local (and async friendly) context manager that uses
contextvars
. The recommended pattern for new code that doesn't care about backwards compatibility would be:For code that wants to be compatible with older versions of Python, the suggested code is:
This change retains
warnings.filters
andwarnings.catch_warnings()
as mechanisms that use process global state. Conceptually, there will be two sets of warning filters, the legacy process globalwarnings.filters
version and the thread localget_context()._filters
version. Perhaps we could eventually warn when the process global versions are used but I think that would be many years in the future.It would be intuitive if the thread local filtering was inherited when a new thread is created. I've created a separate PR that adds this feature to
threading.Thread
: gh-128209. That's a potentially controversial change but I think it's the correct thing to do. It will make contextvars behave similarly between asyncio tasks (which already inherit context) and threads. I think people will expect the warnings context manager to have lexical scope. If you see the code:Then you would assume that
inner_function()
is going to haveMyWarning
filtered, even if it internally spawns a thread.Regarding backwards compatibility, I did a code search and there are quite a few examples for code that manipulates
warnings.filters
directly, either mutating it or assigning a different list to the module global. There is also code that does this:📚 Documentation preview 📚: https://cpython-previews--128300.org.readthedocs.build/