mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	bpo-29102: Add a unique ID to PyInterpreterState. (#1639)
This commit is contained in:
		
							parent
							
								
									93fc20b73e
								
							
						
					
					
						commit
						e377416c10
					
				
					 8 changed files with 152 additions and 7 deletions
				
			
		|  | @ -821,6 +821,14 @@ been created. | ||||||
|    :c:func:`PyThreadState_Clear`. |    :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() | .. c:function:: PyObject* PyThreadState_GetDict() | ||||||
| 
 | 
 | ||||||
|    Return a dictionary in which extensions can store thread-specific state |    Return a dictionary in which extensions can store thread-specific state | ||||||
|  |  | ||||||
|  | @ -28,6 +28,8 @@ typedef struct _is { | ||||||
|     struct _is *next; |     struct _is *next; | ||||||
|     struct _ts *tstate_head; |     struct _ts *tstate_head; | ||||||
| 
 | 
 | ||||||
|  |     int64_t id; | ||||||
|  | 
 | ||||||
|     PyObject *modules; |     PyObject *modules; | ||||||
|     PyObject *modules_by_index; |     PyObject *modules_by_index; | ||||||
|     PyObject *sysdict; |     PyObject *sysdict; | ||||||
|  | @ -154,9 +156,16 @@ typedef struct _ts { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | #ifndef Py_LIMITED_API | ||||||
|  | PyAPI_FUNC(void) _PyInterpreterState_Init(void); | ||||||
|  | #endif /* !Py_LIMITED_API */ | ||||||
| PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_New(void); | PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_New(void); | ||||||
| PyAPI_FUNC(void) PyInterpreterState_Clear(PyInterpreterState *); | PyAPI_FUNC(void) PyInterpreterState_Clear(PyInterpreterState *); | ||||||
| PyAPI_FUNC(void) PyInterpreterState_Delete(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 | #ifndef Py_LIMITED_API | ||||||
| PyAPI_FUNC(int) _PyState_AddModule(PyObject*, struct PyModuleDef*); | PyAPI_FUNC(int) _PyState_AddModule(PyObject*, struct PyModuleDef*); | ||||||
| #endif /* !Py_LIMITED_API */ | #endif /* !Py_LIMITED_API */ | ||||||
|  |  | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| # Run the _testcapi module tests (tests for the Python/C API):  by defn, | # Run the _testcapi module tests (tests for the Python/C API):  by defn, | ||||||
| # these are all functions _testcapi exports whose name begins with 'test_'. | # these are all functions _testcapi exports whose name begins with 'test_'. | ||||||
| 
 | 
 | ||||||
|  | from collections import namedtuple | ||||||
| import os | import os | ||||||
| import pickle | import pickle | ||||||
|  | import platform | ||||||
| import random | import random | ||||||
| import re | import re | ||||||
| import subprocess | import subprocess | ||||||
|  | @ -384,12 +386,91 @@ def run_embedded_interpreter(self, *args): | ||||||
|         return out, err |         return out, err | ||||||
| 
 | 
 | ||||||
|     def test_subinterps(self): |     def test_subinterps(self): | ||||||
|         # This is just a "don't crash" test |  | ||||||
|         out, err = self.run_embedded_interpreter("repeated_init_and_subinterpreters") |         out, err = self.run_embedded_interpreter("repeated_init_and_subinterpreters") | ||||||
|  |         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: |                 if support.verbose: | ||||||
|             print() |                     print(line) | ||||||
|             print(out) |                 lastmain = main | ||||||
|             print(err) |                 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 |     @staticmethod | ||||||
|     def _get_default_pipe_encoding(): |     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 | - bpo-24821: Fixed the slowing down to 25 times in the searching of some | ||||||
|   unlucky Unicode characters. |   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 | - bpo-29894: The deprecation warning is emitted if __complex__ returns an | ||||||
|   instance of a strict subclass of complex.  In a future versions of Python |   instance of a strict subclass of complex.  In a future versions of Python | ||||||
|   this can be an error. |   this can be an error. | ||||||
|  |  | ||||||
|  | @ -538,6 +538,7 @@ EXPORTS | ||||||
|   PySlice_Type=python37.PySlice_Type DATA |   PySlice_Type=python37.PySlice_Type DATA | ||||||
|   PySlice_Unpack=python37.PySlice_Unpack |   PySlice_Unpack=python37.PySlice_Unpack | ||||||
|   PySortWrapper_Type=python37.PySortWrapper_Type DATA |   PySortWrapper_Type=python37.PySortWrapper_Type DATA | ||||||
|  |   PyInterpreterState_GetID=python37.PyInterpreterState_GetID | ||||||
|   PyState_AddModule=python37.PyState_AddModule |   PyState_AddModule=python37.PyState_AddModule | ||||||
|   PyState_FindModule=python37.PyState_FindModule |   PyState_FindModule=python37.PyState_FindModule | ||||||
|   PyState_RemoveModule=python37.PyState_RemoveModule |   PyState_RemoveModule=python37.PyState_RemoveModule | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| #include <Python.h> | #include <Python.h> | ||||||
|  | #include <inttypes.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| 
 | 
 | ||||||
| /*********************************************************
 | /*********************************************************
 | ||||||
|  | @ -22,9 +23,13 @@ static void _testembed_Py_Initialize(void) | ||||||
| 
 | 
 | ||||||
| static void print_subinterp(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(); |     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); |     fflush(stdout); | ||||||
|     PyRun_SimpleString( |     PyRun_SimpleString( | ||||||
|         "import sys;" |         "import sys;" | ||||||
|  |  | ||||||
|  | @ -344,6 +344,7 @@ _Py_InitializeEx_Private(int install_sigs, int install_importlib) | ||||||
| 
 | 
 | ||||||
|     _PyRandom_Init(); |     _PyRandom_Init(); | ||||||
| 
 | 
 | ||||||
|  |     _PyInterpreterState_Init(); | ||||||
|     interp = PyInterpreterState_New(); |     interp = PyInterpreterState_New(); | ||||||
|     if (interp == NULL) |     if (interp == NULL) | ||||||
|         Py_FatalError("Py_Initialize: can't make first interpreter"); |         Py_FatalError("Py_Initialize: can't make first interpreter"); | ||||||
|  |  | ||||||
|  | @ -65,6 +65,23 @@ PyThreadFrameGetter _PyThreadState_GetFrame = NULL; | ||||||
| static void _PyGILState_NoteThreadState(PyThreadState* tstate); | static void _PyGILState_NoteThreadState(PyThreadState* tstate); | ||||||
| #endif | #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 * | ||||||
| PyInterpreterState_New(void) | PyInterpreterState_New(void) | ||||||
|  | @ -103,6 +120,15 @@ PyInterpreterState_New(void) | ||||||
|         HEAD_LOCK(); |         HEAD_LOCK(); | ||||||
|         interp->next = interp_head; |         interp->next = interp_head; | ||||||
|         interp_head = interp; |         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(); |         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 */ | /* Default implementation for _PyThreadState_GetFrame */ | ||||||
| static struct _frame * | static struct _frame * | ||||||
| threadstate_getframe(PyThreadState *self) | threadstate_getframe(PyThreadState *self) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Eric Snow
						Eric Snow