diff options
-rw-r--r-- | Doc/c-api/init.rst | 8 | ||||
-rw-r--r-- | Include/pystate.h | 9 | ||||
-rw-r--r-- | Lib/test/test_capi.py | 91 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | PC/python3.def | 1 | ||||
-rw-r--r-- | Programs/_testembed.c | 9 | ||||
-rw-r--r-- | Python/pylifecycle.c | 1 | ||||
-rw-r--r-- | Python/pystate.c | 37 |
8 files changed, 152 insertions, 7 deletions
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 @@ 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 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 @@ 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 */ 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 @@ 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(): @@ -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. 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 @@ 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 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 _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;" 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 @@ _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"); 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 @@ 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) |