Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/c-api/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,14 @@ been created.
:c:func:`PyThreadState_Clear`.


.. c:function:: PY_INT64_T PyInterpreterState_GetID(PyInterpreterState *interp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python 3.6 now uses stdint.h: please use directly uint64_t.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why unsigned? Did you mean int64_t?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, int64_t. By the way, extract of master:

#define PY_INT64_T int64_t


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
Expand Down
9 changes: 9 additions & 0 deletions Include/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down
91 changes: 86 additions & 5 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -384,12 +386,91 @@ def run_embedded_interpreter(self, *args):
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():
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions PC/python3.def
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <Python.h>
#include <inttypes.h>
#include <stdio.h>

/*********************************************************
Expand All @@ -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;"
Expand Down
1 change: 1 addition & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
37 changes: 37 additions & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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)
Expand Down