Skip to content
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

gh-106320: Remove private _PyCrossInterpreterData API #107068

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
76 changes: 0 additions & 76 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,79 +273,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 interp;
// 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) _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 *);
85 changes: 85 additions & 0 deletions Include/internal/pycore_cross_interp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* Cross-interpreter data */

#ifndef Py_INTERNAL_CROSS_INTERPRETER_DATA_H
#define Py_INTERNAL_CROSS_INTERPRETER_DATA_H
#ifdef __cplusplus
extern "C" {
#endif

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

// _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 interp;
// 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(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) _PyObject_CheckCrossInterpreterData(PyObject *);

/* cross-interpreter data registry */

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

// Export for shared _xxinterpchannels extension
PyAPI_FUNC(int) _PyCrossInterpreterData_RegisterClass(PyTypeObject *, crossinterpdatafunc);
PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);

#ifdef __cplusplus
}
#endif
#endif // !Py_INTERNAL_CROSS_INTERPRETER_DATA_H
1 change: 1 addition & 0 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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_cross_interp.h" // crossinterpdatafunc
#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 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,13 +7,13 @@
import threading
import unittest

import _testcapi
from test import support
from test.support import import_helper
from test.support import script_helper


interpreters = import_helper.import_module('_xxsubinterpreters')
_testinternalcapi = import_helper.import_module('_testinternalcapi')


##################################
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
3 changes: 2 additions & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1750,9 +1750,10 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/pycore_complexobject.h \
$(srcdir)/Include/internal/pycore_condvar.h \
$(srcdir)/Include/internal/pycore_context.h \
$(srcdir)/Include/internal/pycore_cross_interp.h \
$(srcdir)/Include/internal/pycore_descrobject.h \
$(srcdir)/Include/internal/pycore_dict.h \
$(srcdir)/Include/internal/pycore_dict_state.h \
$(srcdir)/Include/internal/pycore_descrobject.h \
$(srcdir)/Include/internal/pycore_dtoa.h \
$(srcdir)/Include/internal/pycore_exceptions.h \
$(srcdir)/Include/internal/pycore_faulthandler.h \
Expand Down
54 changes: 0 additions & 54 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1541,58 +1541,6 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
return PyLong_FromLong(r);
}

static void
_xid_capsule_destructor(PyObject *capsule)
{
_PyCrossInterpreterData *data = \
(_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
if (data != NULL) {
assert(_PyCrossInterpreterData_Release(data) == 0);
PyMem_Free(data);
}
}

static PyObject *
get_crossinterp_data(PyObject *self, PyObject *args)
{
PyObject *obj = NULL;
if (!PyArg_ParseTuple(args, "O:get_crossinterp_data", &obj)) {
return NULL;
}

_PyCrossInterpreterData *data = PyMem_NEW(_PyCrossInterpreterData, 1);
if (data == NULL) {
PyErr_NoMemory();
return NULL;
}
if (_PyObject_GetCrossInterpreterData(obj, data) != 0) {
PyMem_Free(data);
return NULL;
}
PyObject *capsule = PyCapsule_New(data, NULL, _xid_capsule_destructor);
if (capsule == NULL) {
assert(_PyCrossInterpreterData_Release(data) == 0);
PyMem_Free(data);
}
return capsule;
}

static PyObject *
restore_crossinterp_data(PyObject *self, PyObject *args)
{
PyObject *capsule = NULL;
if (!PyArg_ParseTuple(args, "O:restore_crossinterp_data", &capsule)) {
return NULL;
}

_PyCrossInterpreterData *data = \
(_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
if (data == NULL) {
return NULL;
}
return _PyCrossInterpreterData_NewObject(data);
}

static PyMethodDef ml;

static PyObject *
Expand Down Expand Up @@ -3707,8 +3655,6 @@ static PyMethodDef TestMethods[] = {
{"run_in_subinterp_with_config",
_PyCFunction_CAST(run_in_subinterp_with_config),
METH_VARARGS | METH_KEYWORDS},
{"get_crossinterp_data", get_crossinterp_data, METH_VARARGS},
{"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
{"create_cfunction", create_cfunction, METH_NOARGS},
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS,
PyDoc_STR("set_error_class(error_class) -> None")},
Expand Down
58 changes: 58 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "pycore_bytesobject.h" // _PyBytes_Find()
#include "pycore_ceval.h" // _PyEval_AddPendingCall
#include "pycore_compile.h" // _PyCompile_CodeGen, _PyCompile_OptimizeCfg, _PyCompile_Assemble, _PyCompile_CleanDoc
#include "pycore_cross_interp.h" // _PyCrossInterpreterData
#include "pycore_fileutils.h" // _Py_normpath
#include "pycore_frame.h" // _PyInterpreterFrame
#include "pycore_gc.h" // PyGC_Head
Expand Down Expand Up @@ -1446,6 +1447,61 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
}


/* Cross-interpreter data (xid) */

static void
_xid_capsule_destructor(PyObject *capsule)
{
_PyCrossInterpreterData *data = \
(_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
if (data != NULL) {
assert(_PyCrossInterpreterData_Release(data) == 0);
PyMem_Free(data);
}
}

static PyObject *
get_crossinterp_data(PyObject *self, PyObject *args)
{
PyObject *obj = NULL;
if (!PyArg_ParseTuple(args, "O:get_crossinterp_data", &obj)) {
return NULL;
}

_PyCrossInterpreterData *data = PyMem_NEW(_PyCrossInterpreterData, 1);
if (data == NULL) {
PyErr_NoMemory();
return NULL;
}
if (_PyObject_GetCrossInterpreterData(obj, data) != 0) {
PyMem_Free(data);
return NULL;
}
PyObject *capsule = PyCapsule_New(data, NULL, _xid_capsule_destructor);
if (capsule == NULL) {
assert(_PyCrossInterpreterData_Release(data) == 0);
PyMem_Free(data);
}
return capsule;
}

static PyObject *
restore_crossinterp_data(PyObject *self, PyObject *args)
{
PyObject *capsule = NULL;
if (!PyArg_ParseTuple(args, "O:restore_crossinterp_data", &capsule)) {
return NULL;
}

_PyCrossInterpreterData *data = \
(_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
if (data == NULL) {
return NULL;
}
return _PyCrossInterpreterData_NewObject(data);
}


static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
Expand Down Expand Up @@ -1502,6 +1558,8 @@ static PyMethodDef module_functions[] = {
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
{"test_atexit", test_atexit, METH_NOARGS},
{"get_crossinterp_data", get_crossinterp_data, METH_VARARGS},
{"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
{NULL, NULL} /* sentinel */
};

Expand Down
1 change: 1 addition & 0 deletions Modules/_xxinterpchannelsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "Python.h"
#include "pycore_atexit.h" // _Py_AtExit()
#include "pycore_cross_interp.h" // crossinterpdatafunc
#include "pycore_interp_id.h" // _PyInterpreterState_GetIDObject()


Expand Down
1 change: 1 addition & 0 deletions PCbuild/pythoncore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
<ClInclude Include="..\Include\internal\pycore_complexobject.h" />
<ClInclude Include="..\Include\internal\pycore_condvar.h" />
<ClInclude Include="..\Include\internal\pycore_context.h" />
<ClInclude Include="..\Include\internal\pycore_cross_interp.h" />
<ClInclude Include="..\Include\internal\pycore_descrobject.h" />
<ClInclude Include="..\Include\internal\pycore_dict.h" />
<ClInclude Include="..\Include\internal\pycore_dict_state.h" />
Expand Down