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

This commit is contained in:
Eric Snow 2017-05-22 19:46:40 -07:00 committed by GitHub
parent 93fc20b73e
commit e377416c10
8 changed files with 152 additions and 7 deletions

View 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

View 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 */

View 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():

View File

@ -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.

View 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

View 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;"

View 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");

View 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)