Skip to content

Commit

Permalink
pythongh-76785: Move the Cross-Interpreter Code to Its Own File (pyth…
Browse files Browse the repository at this point in the history
…ongh-111502)

This is partly to clear this stuff out of pystate.c, but also in preparation for moving some code out of _xxsubinterpretersmodule.c.  This change also moves this stuff to the internal API (new: Include/internal/pycore_crossinterp.h).  @vstinner did this previously and I undid it.  Now I'm re-doing it. :/
  • Loading branch information
ericsnowcurrently authored and Glyphack committed Jan 27, 2024
1 parent 0ef0b93 commit 537057e
Show file tree
Hide file tree
Showing 18 changed files with 849 additions and 760 deletions.
77 changes: 0 additions & 77 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,80 +258,3 @@ PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc(
PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc(
PyInterpreterState *interp,
_PyFrameEvalFunction eval_frame);


/* cross-interpreter data */

// _PyCrossInterpreterData is similar to Py_buffer as an effectively
// opaque struct that holds data outside the object machinery. This
// is necessary to pass safely between interpreters in the same process.
typedef struct _xid _PyCrossInterpreterData;

typedef PyObject *(*xid_newobjectfunc)(_PyCrossInterpreterData *);
typedef void (*xid_freefunc)(void *);

struct _xid {
// data is the cross-interpreter-safe derivation of a Python object
// (see _PyObject_GetCrossInterpreterData). It will be NULL if the
// new_object func (below) encodes the data.
void *data;
// obj is the Python object from which the data was derived. This
// is non-NULL only if the data remains bound to the object in some
// way, such that the object must be "released" (via a decref) when
// the data is released. In that case the code that sets the field,
// likely a registered "crossinterpdatafunc", is responsible for
// ensuring it owns the reference (i.e. incref).
PyObject *obj;
// interp is the ID of the owning interpreter of the original
// object. It corresponds to the active interpreter when
// _PyObject_GetCrossInterpreterData() was called. This should only
// be set by the cross-interpreter machinery.
//
// We use the ID rather than the PyInterpreterState to avoid issues
// with deleted interpreters. Note that IDs are never re-used, so
// each one will always correspond to a specific interpreter
// (whether still alive or not).
int64_t interpid;
// new_object is a function that returns a new object in the current
// interpreter given the data. The resulting object (a new
// reference) will be equivalent to the original object. This field
// is required.
xid_newobjectfunc new_object;
// free is called when the data is released. If it is NULL then
// nothing will be done to free the data. For some types this is
// okay (e.g. bytes) and for those types this field should be set
// to NULL. However, for most the data was allocated just for
// cross-interpreter use, so it must be freed when
// _PyCrossInterpreterData_Release is called or the memory will
// leak. In that case, at the very least this field should be set
// to PyMem_RawFree (the default if not explicitly set to NULL).
// The call will happen with the original interpreter activated.
xid_freefunc free;
};

PyAPI_FUNC(void) _PyCrossInterpreterData_Init(
_PyCrossInterpreterData *data,
PyInterpreterState *interp, void *shared, PyObject *obj,
xid_newobjectfunc new_object);
PyAPI_FUNC(int) _PyCrossInterpreterData_InitWithSize(
_PyCrossInterpreterData *,
PyInterpreterState *interp, const size_t, PyObject *,
xid_newobjectfunc);
PyAPI_FUNC(void) _PyCrossInterpreterData_Clear(
PyInterpreterState *, _PyCrossInterpreterData *);

PyAPI_FUNC(int) _PyObject_GetCrossInterpreterData(PyObject *, _PyCrossInterpreterData *);
PyAPI_FUNC(PyObject *) _PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *);
PyAPI_FUNC(int) _PyCrossInterpreterData_Release(_PyCrossInterpreterData *);
PyAPI_FUNC(int) _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *);

PyAPI_FUNC(int) _PyObject_CheckCrossInterpreterData(PyObject *);

/* cross-interpreter data registry */

typedef int (*crossinterpdatafunc)(PyThreadState *tstate, PyObject *,
_PyCrossInterpreterData *);

PyAPI_FUNC(int) _PyCrossInterpreterData_RegisterClass(PyTypeObject *, crossinterpdatafunc);
PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);
10 changes: 0 additions & 10 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,6 @@ PyAPI_FUNC(int) _PyEval_AddPendingCall(
void *arg,
int flags);

typedef int (*_Py_simple_func)(void *);
extern int _Py_CallInInterpreter(
PyInterpreterState *interp,
_Py_simple_func func,
void *arg);
extern int _Py_CallInInterpreterAndRawFree(
PyInterpreterState *interp,
_Py_simple_func func,
void *arg);

extern void _PyEval_SignalAsyncExc(PyInterpreterState *interp);
#ifdef HAVE_FORK
extern PyStatus _PyEval_ReInitThreads(PyThreadState *tstate);
Expand Down
139 changes: 139 additions & 0 deletions Include/internal/pycore_crossinterp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#ifndef Py_INTERNAL_CROSSINTERP_H
#define Py_INTERNAL_CROSSINTERP_H
#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif


/***************************/
/* cross-interpreter calls */
/***************************/

typedef int (*_Py_simple_func)(void *);
extern int _Py_CallInInterpreter(
PyInterpreterState *interp,
_Py_simple_func func,
void *arg);
extern int _Py_CallInInterpreterAndRawFree(
PyInterpreterState *interp,
_Py_simple_func func,
void *arg);


/**************************/
/* cross-interpreter data */
/**************************/

typedef struct _xid _PyCrossInterpreterData;
typedef PyObject *(*xid_newobjectfunc)(_PyCrossInterpreterData *);
typedef void (*xid_freefunc)(void *);

// _PyCrossInterpreterData is similar to Py_buffer as an effectively
// opaque struct that holds data outside the object machinery. This
// is necessary to pass safely between interpreters in the same process.
struct _xid {
// data is the cross-interpreter-safe derivation of a Python object
// (see _PyObject_GetCrossInterpreterData). It will be NULL if the
// new_object func (below) encodes the data.
void *data;
// obj is the Python object from which the data was derived. This
// is non-NULL only if the data remains bound to the object in some
// way, such that the object must be "released" (via a decref) when
// the data is released. In that case the code that sets the field,
// likely a registered "crossinterpdatafunc", is responsible for
// ensuring it owns the reference (i.e. incref).
PyObject *obj;
// interp is the ID of the owning interpreter of the original
// object. It corresponds to the active interpreter when
// _PyObject_GetCrossInterpreterData() was called. This should only
// be set by the cross-interpreter machinery.
//
// We use the ID rather than the PyInterpreterState to avoid issues
// with deleted interpreters. Note that IDs are never re-used, so
// each one will always correspond to a specific interpreter
// (whether still alive or not).
int64_t interpid;
// new_object is a function that returns a new object in the current
// interpreter given the data. The resulting object (a new
// reference) will be equivalent to the original object. This field
// is required.
xid_newobjectfunc new_object;
// free is called when the data is released. If it is NULL then
// nothing will be done to free the data. For some types this is
// okay (e.g. bytes) and for those types this field should be set
// to NULL. However, for most the data was allocated just for
// cross-interpreter use, so it must be freed when
// _PyCrossInterpreterData_Release is called or the memory will
// leak. In that case, at the very least this field should be set
// to PyMem_RawFree (the default if not explicitly set to NULL).
// The call will happen with the original interpreter activated.
xid_freefunc free;
};

PyAPI_FUNC(_PyCrossInterpreterData *) _PyCrossInterpreterData_New(void);
PyAPI_FUNC(void) _PyCrossInterpreterData_Free(_PyCrossInterpreterData *data);


/* defining cross-interpreter data */

PyAPI_FUNC(void) _PyCrossInterpreterData_Init(
_PyCrossInterpreterData *data,
PyInterpreterState *interp, void *shared, PyObject *obj,
xid_newobjectfunc new_object);
PyAPI_FUNC(int) _PyCrossInterpreterData_InitWithSize(
_PyCrossInterpreterData *,
PyInterpreterState *interp, const size_t, PyObject *,
xid_newobjectfunc);
PyAPI_FUNC(void) _PyCrossInterpreterData_Clear(
PyInterpreterState *, _PyCrossInterpreterData *);


/* using cross-interpreter data */

PyAPI_FUNC(int) _PyObject_CheckCrossInterpreterData(PyObject *);
PyAPI_FUNC(int) _PyObject_GetCrossInterpreterData(PyObject *, _PyCrossInterpreterData *);
PyAPI_FUNC(PyObject *) _PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *);
PyAPI_FUNC(int) _PyCrossInterpreterData_Release(_PyCrossInterpreterData *);
PyAPI_FUNC(int) _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *);


/* cross-interpreter data registry */

// For now we use a global registry of shareable classes. An
// alternative would be to add a tp_* slot for a class's
// crossinterpdatafunc. It would be simpler and more efficient.

typedef int (*crossinterpdatafunc)(PyThreadState *tstate, PyObject *,
_PyCrossInterpreterData *);

struct _xidregitem;

struct _xidregitem {
struct _xidregitem *prev;
struct _xidregitem *next;
/* This can be a dangling pointer, but only if weakref is set. */
PyTypeObject *cls;
/* This is NULL for builtin types. */
PyObject *weakref;
size_t refcount;
crossinterpdatafunc getdata;
};

struct _xidregistry {
PyThread_type_lock mutex;
struct _xidregitem *head;
};

PyAPI_FUNC(int) _PyCrossInterpreterData_RegisterClass(PyTypeObject *, crossinterpdatafunc);
PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);


#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_CROSSINTERP_H */
23 changes: 1 addition & 22 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ extern "C" {
#include "pycore_ceval_state.h" // struct _ceval_state
#include "pycore_code.h" // struct callable_cache
#include "pycore_context.h" // struct _Py_context_state
#include "pycore_crossinterp.h" // struct _xidregistry
#include "pycore_dict_state.h" // struct _Py_dict_state
#include "pycore_dtoa.h" // struct _dtoa_state
#include "pycore_exceptions.h" // struct _Py_exc_state
Expand All @@ -41,28 +42,6 @@ struct _Py_long_state {

/* cross-interpreter data registry */

/* For now we use a global registry of shareable classes. An
alternative would be to add a tp_* slot for a class's
crossinterpdatafunc. It would be simpler and more efficient. */

struct _xidregitem;

struct _xidregitem {
struct _xidregitem *prev;
struct _xidregitem *next;
/* This can be a dangling pointer, but only if weakref is set. */
PyTypeObject *cls;
/* This is NULL for builtin types. */
PyObject *weakref;
size_t refcount;
crossinterpdatafunc getdata;
};

struct _xidregistry {
PyThread_type_lock mutex;
struct _xidregitem *head;
};


/* interpreter state */

Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extern "C" {

#include "pycore_atexit.h" // struct _atexit_runtime_state
#include "pycore_ceval_state.h" // struct _ceval_runtime_state
#include "pycore_crossinterp.h" // struct _xidregistry
#include "pycore_faulthandler.h" // struct _faulthandler_runtime_state
#include "pycore_floatobject.h" // struct _Py_float_runtime_state
#include "pycore_import.h" // struct _import_runtime_state
Expand Down
12 changes: 6 additions & 6 deletions Lib/test/test__xxsubinterpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import threading
import unittest

import _testcapi
import _testinternalcapi
from test import support
from test.support import import_helper
from test.support import script_helper
Expand Down Expand Up @@ -146,17 +146,17 @@ class ShareableTypeTests(unittest.TestCase):
def _assert_values(self, values):
for obj in values:
with self.subTest(obj):
xid = _testcapi.get_crossinterp_data(obj)
got = _testcapi.restore_crossinterp_data(xid)
xid = _testinternalcapi.get_crossinterp_data(obj)
got = _testinternalcapi.restore_crossinterp_data(xid)

self.assertEqual(got, obj)
self.assertIs(type(got), type(obj))

def test_singletons(self):
for obj in [None]:
with self.subTest(obj):
xid = _testcapi.get_crossinterp_data(obj)
got = _testcapi.restore_crossinterp_data(xid)
xid = _testinternalcapi.get_crossinterp_data(obj)
got = _testinternalcapi.restore_crossinterp_data(xid)

# XXX What about between interpreters?
self.assertIs(got, obj)
Expand Down Expand Up @@ -187,7 +187,7 @@ def test_non_shareable_int(self):
for i in ints:
with self.subTest(i):
with self.assertRaises(OverflowError):
_testcapi.get_crossinterp_data(i)
_testinternalcapi.get_crossinterp_data(i)


class ModuleTests(TestBase):
Expand Down
2 changes: 2 additions & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ PYTHON_OBJS= \
Python/codecs.o \
Python/compile.o \
Python/context.o \
Python/crossinterp.o \
Python/dynamic_annotations.o \
Python/errors.o \
Python/executor.o \
Expand Down Expand Up @@ -1800,6 +1801,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/pycore_complexobject.h \
$(srcdir)/Include/internal/pycore_condvar.h \
$(srcdir)/Include/internal/pycore_context.h \
$(srcdir)/Include/internal/pycore_crossinterp.h \
$(srcdir)/Include/internal/pycore_dict.h \
$(srcdir)/Include/internal/pycore_dict_state.h \
$(srcdir)/Include/internal/pycore_descrobject.h \
Expand Down
Loading

0 comments on commit 537057e

Please sign in to comment.