bpo-29102: Add a unique ID to PyInterpreterState. (#1639)

diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst
index 56cf77a..ca7636b 100644
--- a/Doc/c-api/init.rst
+++ b/Doc/c-api/init.rst
@@ -821,6 +821,14 @@
    :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
diff --git a/Include/pystate.h b/Include/pystate.h
index 62254fa..9170ba9 100644
--- a/Include/pystate.h
+++ b/Include/pystate.h
@@ -28,6 +28,8 @@
     struct _is *next;
     struct _ts *tstate_head;
 
+    int64_t id;
+
     PyObject *modules;
     PyObject *modules_by_index;
     PyObject *sysdict;
@@ -154,9 +156,16 @@
 #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 */
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index eb3e2c5..7660981 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -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 @@
         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():
diff --git a/Misc/NEWS b/Misc/NEWS
index 34f1c15..5b41dcf 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -53,6 +53,9 @@
 - 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.
diff --git a/PC/python3.def b/PC/python3.def
index bb3ca88..ad65294 100644
--- a/PC/python3.def
+++ b/PC/python3.def
@@ -538,6 +538,7 @@
   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
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index a68d4fa..de88404 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -1,4 +1,5 @@
 #include <Python.h>
+#include <inttypes.h>
 #include <stdio.h>
 
 /*********************************************************
@@ -22,9 +23,13 @@
 
 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;"
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index c0f41b3..90f8551 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -344,6 +344,7 @@
 
     _PyRandom_Init();
 
+    _PyInterpreterState_Init();
     interp = PyInterpreterState_New();
     if (interp == NULL)
         Py_FatalError("Py_Initialize: can't make first interpreter");
diff --git a/Python/pystate.c b/Python/pystate.c
index 52899f1..99a579a 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -65,6 +65,23 @@
 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 @@
         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 @@
 }
 
 
+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)