]> granicus.if.org Git - python/commitdiff
bpo-32591: fix abort in _PyErr_WarnUnawaitedCoroutine during shutdown (#5337)
authorNathaniel J. Smith <njs@pobox.com>
Fri, 26 Jan 2018 19:28:31 +0000 (11:28 -0800)
committerYury Selivanov <yury@magic.io>
Fri, 26 Jan 2018 19:28:31 +0000 (14:28 -0500)
When an unawaited coroutine is collected very late in shutdown --
like, during the final GC at the end of PyImport_Cleanup -- then it
was triggering an interpreter abort, because we'd try to look up the
"warnings" module and not only was it missing (we were prepared for
that), but the entire module system was missing (which we were not
prepared for).

I've tried to fix this at the source, by making the utility function
get_warnings_attr robust against this in general. Note that it already
has the convention that it can return NULL without setting an error,
which is how it signals that the attribute it was asked to fetch is
missing, and that all callers already check for NULL returns.

There's a similar check for being late in shutdown at the top of
warn_explicit, which might be unnecessary after this fix, but I'm not
sure so I'm going to leave it.

Lib/test/test_coroutines.py
Python/_warnings.c

index 8a531b888924e3d431c6a04c07096c3c7aee0c93..a97535a12a8961b154133a73d7e2897b8db5ed54 100644 (file)
@@ -8,6 +8,7 @@ import types
 import unittest
 import warnings
 from test import support
+from test.support.script_helper import assert_python_ok
 
 
 class AsyncYieldFrom:
@@ -2168,6 +2169,27 @@ class OriginTrackingTest(unittest.TestCase):
         finally:
             warnings._warn_unawaited_coroutine = orig_wuc
 
+
+class UnawaitedWarningDuringShutdownTest(unittest.TestCase):
+    # https://bugs.python.org/issue32591#msg310726
+    def test_unawaited_warning_during_shutdown(self):
+        code = ("import asyncio\n"
+                "async def f(): pass\n"
+                "asyncio.gather(f())\n")
+        assert_python_ok("-c", code)
+
+        code = ("import sys\n"
+                "async def f(): pass\n"
+                "sys.coro = f()\n")
+        assert_python_ok("-c", code)
+
+        code = ("import sys\n"
+                "async def f(): pass\n"
+                "sys.corocycle = [f()]\n"
+                "sys.corocycle.append(sys.corocycle)\n")
+        assert_python_ok("-c", code)
+
+
 @support.cpython_only
 class CAPITest(unittest.TestCase):
 
index 16ae932450cc010604a2585dc4947d812a0f0374..0568af4df5a80b200c5d91ba8c1f5d435f27f3a6 100644 (file)
@@ -75,6 +75,13 @@ get_warnings_attr(_Py_Identifier *attr_id, int try_import)
         }
     }
     else {
+        /* if we're so late into Python finalization that the module dict is
+           gone, then we can't even use PyImport_GetModule without triggering
+           an interpreter abort.
+        */
+        if (!PyThreadState_GET()->interp->modules) {
+            return NULL;
+        }
         warnings_module = PyImport_GetModule(warnings_str);
         if (warnings_module == NULL)
             return NULL;