]> granicus.if.org Git - python/commitdiff
bpo-29102: Add a unique ID to PyInterpreterState. (#1639)
authorEric Snow <ericsnowcurrently@gmail.com>
Tue, 23 May 2017 02:46:40 +0000 (19:46 -0700)
committerGitHub <noreply@github.com>
Tue, 23 May 2017 02:46:40 +0000 (19:46 -0700)
Doc/c-api/init.rst
Include/pystate.h
Lib/test/test_capi.py
Misc/NEWS
PC/python3.def
Programs/_testembed.c
Python/pylifecycle.c
Python/pystate.c

index 56cf77affe60428d03ba3dd1ebe2ef75b978a431..ca7636b6f5a5f2fd6d181c83cadb64976424df23 100644 (file)
@@ -821,6 +821,14 @@ been created.
    :c:func:`PyThreadState_Clear`.
 
 
+.. c:function:: PY_INT64_T PyInterpreterState_GetID(PyInterpreterState *interp)
+
+   Return the interpreter's unique ID.  If there was any error in doing
+   so then -1 is returned and an error is set.
+
+   .. versionadded:: 3.7
+
+
 .. c:function:: PyObject* PyThreadState_GetDict()
 
    Return a dictionary in which extensions can store thread-specific state
index 62254fab96364efbe1b58306f410f82c64ab3177..9170ba9f7a77b8c0e14459f5e97b111bb88b4fe3 100644 (file)
@@ -28,6 +28,8 @@ typedef struct _is {
     struct _is *next;
     struct _ts *tstate_head;
 
+    int64_t id;
+
     PyObject *modules;
     PyObject *modules_by_index;
     PyObject *sysdict;
@@ -154,9 +156,16 @@ typedef struct _ts {
 #endif
 
 
+#ifndef Py_LIMITED_API
+PyAPI_FUNC(void) _PyInterpreterState_Init(void);
+#endif /* !Py_LIMITED_API */
 PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_New(void);
 PyAPI_FUNC(void) PyInterpreterState_Clear(PyInterpreterState *);
 PyAPI_FUNC(void) PyInterpreterState_Delete(PyInterpreterState *);
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03070000
+/* New in 3.7 */
+PyAPI_FUNC(int64_t) PyInterpreterState_GetID(PyInterpreterState *);
+#endif
 #ifndef Py_LIMITED_API
 PyAPI_FUNC(int) _PyState_AddModule(PyObject*, struct PyModuleDef*);
 #endif /* !Py_LIMITED_API */
index eb3e2c558d88e1a0ed2467908597d3c78052ba90..766098134c4ec670fb8af355ba11073ca1996d5a 100644 (file)
@@ -1,8 +1,10 @@
 # Run the _testcapi module tests (tests for the Python/C API):  by defn,
 # these are all functions _testcapi exports whose name begins with 'test_'.
 
+from collections import namedtuple
 import os
 import pickle
+import platform
 import random
 import re
 import subprocess
@@ -384,12 +386,91 @@ class EmbeddingTests(unittest.TestCase):
         return out, err
 
     def test_subinterps(self):
-        # This is just a "don't crash" test
         out, err = self.run_embedded_interpreter("repeated_init_and_subinterpreters")
-        if support.verbose:
-            print()
-            print(out)
-            print(err)
+        self.assertEqual(err, "")
+
+        # The output from _testembed looks like this:
+        # --- Pass 0 ---
+        # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
+        # interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
+        # interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
+        # interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
+        # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
+        # --- Pass 1 ---
+        # ...
+
+        interp_pat = (r"^interp (\d+) <(0x[\dA-F]+)>, "
+                      r"thread state <(0x[\dA-F]+)>: "
+                      r"id\(modules\) = ([\d]+)$")
+        Interp = namedtuple("Interp", "id interp tstate modules")
+
+        main = None
+        lastmain = None
+        numinner = None
+        numloops = 0
+        for line in out.splitlines():
+            if line == "--- Pass {} ---".format(numloops):
+                if numinner is not None:
+                    self.assertEqual(numinner, 5)
+                if support.verbose:
+                    print(line)
+                lastmain = main
+                main = None
+                mainid = 0
+                numloops += 1
+                numinner = 0
+                continue
+            numinner += 1
+
+            self.assertLessEqual(numinner, 5)
+            match = re.match(interp_pat, line)
+            if match is None:
+                self.assertRegex(line, interp_pat)
+
+            # The last line in the loop should be the same as the first.
+            if numinner == 5:
+                self.assertEqual(match.groups(), main)
+                continue
+
+            # Parse the line from the loop.  The first line is the main
+            # interpreter and the 3 afterward are subinterpreters.
+            interp = Interp(*match.groups())
+            if support.verbose:
+                print(interp)
+            if numinner == 1:
+                main = interp
+                id = str(mainid)
+            else:
+                subid = mainid + numinner - 1
+                id = str(subid)
+
+            # Validate the loop line for each interpreter.
+            self.assertEqual(interp.id, id)
+            self.assertTrue(interp.interp)
+            self.assertTrue(interp.tstate)
+            self.assertTrue(interp.modules)
+            if platform.system() == 'Windows':
+                # XXX Fix on Windows: something is going on with the
+                # pointers in Programs/_testembed.c.  interp.interp
+                # is 0x0 and # interp.modules is the same between
+                # interpreters.
+                continue
+            if interp is main:
+                if lastmain is not None:
+                    # A new main interpreter may have the same interp
+                    # and/or tstate pointer as an earlier finalized/
+                    # destroyed one.  So we do not check interp or
+                    # tstate here.
+                    self.assertNotEqual(interp.modules, lastmain.modules)
+            else:
+                # A new subinterpreter may have the same
+                # PyInterpreterState pointer as a previous one if
+                # the earlier one has already been destroyed.  So
+                # we compare with the main interpreter.  The same
+                # applies to tstate.
+                self.assertNotEqual(interp.interp, main.interp)
+                self.assertNotEqual(interp.tstate, main.tstate)
+                self.assertNotEqual(interp.modules, main.modules)
 
     @staticmethod
     def _get_default_pipe_encoding():
index 34f1c1561ac82b413ce28dcaff150208d663f54d..5b41dcf61dfa58026416c33a13f26ffa9fb3961f 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -53,6 +53,9 @@ Core and Builtins
 - bpo-24821: Fixed the slowing down to 25 times in the searching of some
   unlucky Unicode characters.
 
+- bpo-29102: Add a unique ID to PyInterpreterState.  This makes it easier
+  to identify each subinterpreter.
+
 - bpo-29894: The deprecation warning is emitted if __complex__ returns an
   instance of a strict subclass of complex.  In a future versions of Python
   this can be an error.
index bb3ca8814b4cef13aac1db3fd79130ea2adc614c..ad65294045f67779b88df26b1cc06fabf15df163 100644 (file)
@@ -538,6 +538,7 @@ EXPORTS
   PySlice_Type=python37.PySlice_Type DATA
   PySlice_Unpack=python37.PySlice_Unpack
   PySortWrapper_Type=python37.PySortWrapper_Type DATA
+  PyInterpreterState_GetID=python37.PyInterpreterState_GetID
   PyState_AddModule=python37.PyState_AddModule
   PyState_FindModule=python37.PyState_FindModule
   PyState_RemoveModule=python37.PyState_RemoveModule
index a68d4fa25f7cd05f8d112e6b0d286e64d7eef392..de88404465a75b151d5befd7aaae5fdef90ddc54 100644 (file)
@@ -1,4 +1,5 @@
 #include <Python.h>
+#include <inttypes.h>
 #include <stdio.h>
 
 /*********************************************************
@@ -22,9 +23,13 @@ static void _testembed_Py_Initialize(void)
 
 static void print_subinterp(void)
 {
-    /* Just output some debug stuff */
+    /* Output information about the interpreter in the format
+       expected in Lib/test/test_capi.py (test_subinterps). */
     PyThreadState *ts = PyThreadState_Get();
-    printf("interp %p, thread state %p: ", ts->interp, ts);
+    PyInterpreterState *interp = ts->interp;
+    int64_t id = PyInterpreterState_GetID(interp);
+    printf("interp %lu <0x%" PRIXPTR ">, thread state <0x%" PRIXPTR ">: ",
+            id, (uintptr_t)interp, (uintptr_t)ts);
     fflush(stdout);
     PyRun_SimpleString(
         "import sys;"
index c0f41b3ca73463e5049f8bcef692501893aeefa2..90f85518379f677e536fd6cb4b11e55e8b26e583 100644 (file)
@@ -344,6 +344,7 @@ _Py_InitializeEx_Private(int install_sigs, int install_importlib)
 
     _PyRandom_Init();
 
+    _PyInterpreterState_Init();
     interp = PyInterpreterState_New();
     if (interp == NULL)
         Py_FatalError("Py_Initialize: can't make first interpreter");
index 52899f124a0aba8f2c6b7690fb9c0faaebb6ef2c..99a579a069193aff98b65eec39dbbda9c667168f 100644 (file)
@@ -65,6 +65,23 @@ PyThreadFrameGetter _PyThreadState_GetFrame = NULL;
 static void _PyGILState_NoteThreadState(PyThreadState* tstate);
 #endif
 
+/* _next_interp_id is an auto-numbered sequence of small integers.
+   It gets initialized in _PyInterpreterState_Init(), which is called
+   in Py_Initialize(), and used in PyInterpreterState_New().  A negative
+   interpreter ID indicates an error occurred.  The main interpreter
+   will always have an ID of 0.  Overflow results in a RuntimeError.
+   If that becomes a problem later then we can adjust, e.g. by using
+   a Python int.
+
+   We initialize this to -1 so that the pre-Py_Initialize() value
+   results in an error. */
+static int64_t _next_interp_id = -1;
+
+void
+_PyInterpreterState_Init(void)
+{
+    _next_interp_id = 0;
+}
 
 PyInterpreterState *
 PyInterpreterState_New(void)
@@ -103,6 +120,15 @@ PyInterpreterState_New(void)
         HEAD_LOCK();
         interp->next = interp_head;
         interp_head = interp;
+        if (_next_interp_id < 0) {
+            /* overflow or Py_Initialize() not called! */
+            PyErr_SetString(PyExc_RuntimeError,
+                            "failed to get an interpreter ID");
+            interp = NULL;
+        } else {
+            interp->id = _next_interp_id;
+            _next_interp_id += 1;
+        }
         HEAD_UNLOCK();
     }
 
@@ -170,6 +196,17 @@ PyInterpreterState_Delete(PyInterpreterState *interp)
 }
 
 
+int64_t
+PyInterpreterState_GetID(PyInterpreterState *interp)
+{
+    if (interp == NULL) {
+        PyErr_SetString(PyExc_RuntimeError, "no interpreter provided");
+        return -1;
+    }
+    return interp->id;
+}
+
+
 /* Default implementation for _PyThreadState_GetFrame */
 static struct _frame *
 threadstate_getframe(PyThreadState *self)