mirror of https://github.com/python/cpython
gh-76785: Move the Cross-Interpreter Code to Its Own File (gh-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. :/
This commit is contained in:
parent
7b153d14ef
commit
c6fe0869ab
|
@ -258,80 +258,3 @@ PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc(
|
||||||
PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc(
|
PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc(
|
||||||
PyInterpreterState *interp,
|
PyInterpreterState *interp,
|
||||||
_PyFrameEvalFunction eval_frame);
|
_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 *);
|
|
||||||
|
|
|
@ -54,16 +54,6 @@ PyAPI_FUNC(int) _PyEval_AddPendingCall(
|
||||||
void *arg,
|
void *arg,
|
||||||
int flags);
|
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);
|
extern void _PyEval_SignalAsyncExc(PyInterpreterState *interp);
|
||||||
#ifdef HAVE_FORK
|
#ifdef HAVE_FORK
|
||||||
extern PyStatus _PyEval_ReInitThreads(PyThreadState *tstate);
|
extern PyStatus _PyEval_ReInitThreads(PyThreadState *tstate);
|
||||||
|
|
|
@ -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 */
|
|
@ -15,6 +15,7 @@ extern "C" {
|
||||||
#include "pycore_ceval_state.h" // struct _ceval_state
|
#include "pycore_ceval_state.h" // struct _ceval_state
|
||||||
#include "pycore_code.h" // struct callable_cache
|
#include "pycore_code.h" // struct callable_cache
|
||||||
#include "pycore_context.h" // struct _Py_context_state
|
#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_dict_state.h" // struct _Py_dict_state
|
||||||
#include "pycore_dtoa.h" // struct _dtoa_state
|
#include "pycore_dtoa.h" // struct _dtoa_state
|
||||||
#include "pycore_exceptions.h" // struct _Py_exc_state
|
#include "pycore_exceptions.h" // struct _Py_exc_state
|
||||||
|
@ -41,28 +42,6 @@ struct _Py_long_state {
|
||||||
|
|
||||||
/* cross-interpreter data registry */
|
/* 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 */
|
/* interpreter state */
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ extern "C" {
|
||||||
|
|
||||||
#include "pycore_atexit.h" // struct _atexit_runtime_state
|
#include "pycore_atexit.h" // struct _atexit_runtime_state
|
||||||
#include "pycore_ceval_state.h" // struct _ceval_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_faulthandler.h" // struct _faulthandler_runtime_state
|
||||||
#include "pycore_floatobject.h" // struct _Py_float_runtime_state
|
#include "pycore_floatobject.h" // struct _Py_float_runtime_state
|
||||||
#include "pycore_import.h" // struct _import_runtime_state
|
#include "pycore_import.h" // struct _import_runtime_state
|
||||||
|
|
|
@ -7,7 +7,7 @@ from textwrap import dedent
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import _testcapi
|
import _testinternalcapi
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import import_helper
|
from test.support import import_helper
|
||||||
from test.support import script_helper
|
from test.support import script_helper
|
||||||
|
@ -146,8 +146,8 @@ class ShareableTypeTests(unittest.TestCase):
|
||||||
def _assert_values(self, values):
|
def _assert_values(self, values):
|
||||||
for obj in values:
|
for obj in values:
|
||||||
with self.subTest(obj):
|
with self.subTest(obj):
|
||||||
xid = _testcapi.get_crossinterp_data(obj)
|
xid = _testinternalcapi.get_crossinterp_data(obj)
|
||||||
got = _testcapi.restore_crossinterp_data(xid)
|
got = _testinternalcapi.restore_crossinterp_data(xid)
|
||||||
|
|
||||||
self.assertEqual(got, obj)
|
self.assertEqual(got, obj)
|
||||||
self.assertIs(type(got), type(obj))
|
self.assertIs(type(got), type(obj))
|
||||||
|
@ -155,8 +155,8 @@ class ShareableTypeTests(unittest.TestCase):
|
||||||
def test_singletons(self):
|
def test_singletons(self):
|
||||||
for obj in [None]:
|
for obj in [None]:
|
||||||
with self.subTest(obj):
|
with self.subTest(obj):
|
||||||
xid = _testcapi.get_crossinterp_data(obj)
|
xid = _testinternalcapi.get_crossinterp_data(obj)
|
||||||
got = _testcapi.restore_crossinterp_data(xid)
|
got = _testinternalcapi.restore_crossinterp_data(xid)
|
||||||
|
|
||||||
# XXX What about between interpreters?
|
# XXX What about between interpreters?
|
||||||
self.assertIs(got, obj)
|
self.assertIs(got, obj)
|
||||||
|
@ -187,7 +187,7 @@ class ShareableTypeTests(unittest.TestCase):
|
||||||
for i in ints:
|
for i in ints:
|
||||||
with self.subTest(i):
|
with self.subTest(i):
|
||||||
with self.assertRaises(OverflowError):
|
with self.assertRaises(OverflowError):
|
||||||
_testcapi.get_crossinterp_data(i)
|
_testinternalcapi.get_crossinterp_data(i)
|
||||||
|
|
||||||
|
|
||||||
class ModuleTests(TestBase):
|
class ModuleTests(TestBase):
|
||||||
|
|
|
@ -409,6 +409,7 @@ PYTHON_OBJS= \
|
||||||
Python/codecs.o \
|
Python/codecs.o \
|
||||||
Python/compile.o \
|
Python/compile.o \
|
||||||
Python/context.o \
|
Python/context.o \
|
||||||
|
Python/crossinterp.o \
|
||||||
Python/dynamic_annotations.o \
|
Python/dynamic_annotations.o \
|
||||||
Python/errors.o \
|
Python/errors.o \
|
||||||
Python/executor.o \
|
Python/executor.o \
|
||||||
|
@ -1800,6 +1801,7 @@ PYTHON_HEADERS= \
|
||||||
$(srcdir)/Include/internal/pycore_complexobject.h \
|
$(srcdir)/Include/internal/pycore_complexobject.h \
|
||||||
$(srcdir)/Include/internal/pycore_condvar.h \
|
$(srcdir)/Include/internal/pycore_condvar.h \
|
||||||
$(srcdir)/Include/internal/pycore_context.h \
|
$(srcdir)/Include/internal/pycore_context.h \
|
||||||
|
$(srcdir)/Include/internal/pycore_crossinterp.h \
|
||||||
$(srcdir)/Include/internal/pycore_dict.h \
|
$(srcdir)/Include/internal/pycore_dict.h \
|
||||||
$(srcdir)/Include/internal/pycore_dict_state.h \
|
$(srcdir)/Include/internal/pycore_dict_state.h \
|
||||||
$(srcdir)/Include/internal/pycore_descrobject.h \
|
$(srcdir)/Include/internal/pycore_descrobject.h \
|
||||||
|
|
|
@ -1445,58 +1445,6 @@ run_in_subinterp(PyObject *self, PyObject *args)
|
||||||
return PyLong_FromLong(r);
|
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 PyMethodDef ml;
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -3282,8 +3230,6 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"crash_no_current_thread", crash_no_current_thread, METH_NOARGS},
|
{"crash_no_current_thread", crash_no_current_thread, METH_NOARGS},
|
||||||
{"test_current_tstate_matches", test_current_tstate_matches, METH_NOARGS},
|
{"test_current_tstate_matches", test_current_tstate_matches, METH_NOARGS},
|
||||||
{"run_in_subinterp", run_in_subinterp, METH_VARARGS},
|
{"run_in_subinterp", run_in_subinterp, METH_VARARGS},
|
||||||
{"get_crossinterp_data", get_crossinterp_data, METH_VARARGS},
|
|
||||||
{"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
|
|
||||||
{"create_cfunction", create_cfunction, METH_NOARGS},
|
{"create_cfunction", create_cfunction, METH_NOARGS},
|
||||||
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS,
|
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS,
|
||||||
PyDoc_STR("set_error_class(error_class) -> None")},
|
PyDoc_STR("set_error_class(error_class) -> None")},
|
||||||
|
|
|
@ -1466,6 +1466,58 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
_xid_capsule_destructor(PyObject *capsule)
|
||||||
|
{
|
||||||
|
_PyCrossInterpreterData *data = \
|
||||||
|
(_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
|
||||||
|
if (data != NULL) {
|
||||||
|
assert(_PyCrossInterpreterData_Release(data) == 0);
|
||||||
|
_PyCrossInterpreterData_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 = _PyCrossInterpreterData_New();
|
||||||
|
if (data == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (_PyObject_GetCrossInterpreterData(obj, data) != 0) {
|
||||||
|
_PyCrossInterpreterData_Free(data);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyObject *capsule = PyCapsule_New(data, NULL, _xid_capsule_destructor);
|
||||||
|
if (capsule == NULL) {
|
||||||
|
assert(_PyCrossInterpreterData_Release(data) == 0);
|
||||||
|
_PyCrossInterpreterData_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_testinternalcapi.write_unraisable_exc
|
_testinternalcapi.write_unraisable_exc
|
||||||
exception as exc: object
|
exception as exc: object
|
||||||
|
@ -1645,6 +1697,8 @@ static PyMethodDef module_functions[] = {
|
||||||
METH_VARARGS | METH_KEYWORDS},
|
METH_VARARGS | METH_KEYWORDS},
|
||||||
{"compile_perf_trampoline_entry", compile_perf_trampoline_entry, METH_VARARGS},
|
{"compile_perf_trampoline_entry", compile_perf_trampoline_entry, METH_VARARGS},
|
||||||
{"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS},
|
{"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS},
|
||||||
|
{"get_crossinterp_data", get_crossinterp_data, METH_VARARGS},
|
||||||
|
{"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
|
||||||
_TESTINTERNALCAPI_WRITE_UNRAISABLE_EXC_METHODDEF
|
_TESTINTERNALCAPI_WRITE_UNRAISABLE_EXC_METHODDEF
|
||||||
_TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF
|
_TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "interpreteridobject.h"
|
#include "interpreteridobject.h"
|
||||||
|
#include "pycore_crossinterp.h" // struct _xid
|
||||||
#include "pycore_pybuffer.h" // _PyBuffer_ReleaseInInterpreterAndRawFree()
|
#include "pycore_pybuffer.h" // _PyBuffer_ReleaseInInterpreterAndRawFree()
|
||||||
#include "pycore_interp.h" // _PyInterpreterState_LookUpID()
|
#include "pycore_interp.h" // _PyInterpreterState_LookUpID()
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
#include "pycore_crossinterp.h" // struct _xid
|
||||||
#include "pycore_initconfig.h" // _PyErr_SetFromPyStatus()
|
#include "pycore_initconfig.h" // _PyErr_SetFromPyStatus()
|
||||||
#include "pycore_modsupport.h" // _PyArg_BadArgument()
|
#include "pycore_modsupport.h" // _PyArg_BadArgument()
|
||||||
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
|
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "pycore_pybuffer.h"
|
#include "pycore_pybuffer.h"
|
||||||
#include "pycore_call.h" // _PyObject_CallNoArgs()
|
#include "pycore_call.h" // _PyObject_CallNoArgs()
|
||||||
#include "pycore_ceval.h" // _Py_EnterRecursiveCallTstate()
|
#include "pycore_ceval.h" // _Py_EnterRecursiveCallTstate()
|
||||||
|
#include "pycore_crossinterp.h" // _Py_CallInInterpreter()
|
||||||
#include "pycore_object.h" // _Py_CheckSlotResult()
|
#include "pycore_object.h" // _Py_CheckSlotResult()
|
||||||
#include "pycore_long.h" // _Py_IsNegative
|
#include "pycore_long.h" // _Py_IsNegative
|
||||||
#include "pycore_pyerrors.h" // _PyErr_Occurred()
|
#include "pycore_pyerrors.h" // _PyErr_Occurred()
|
||||||
|
|
|
@ -209,6 +209,7 @@
|
||||||
<ClCompile Include="..\Python\codecs.c" />
|
<ClCompile Include="..\Python\codecs.c" />
|
||||||
<ClCompile Include="..\Python\compile.c" />
|
<ClCompile Include="..\Python\compile.c" />
|
||||||
<ClCompile Include="..\Python\context.c" />
|
<ClCompile Include="..\Python\context.c" />
|
||||||
|
<ClCompile Include="..\Python\crossinterp.c" />
|
||||||
<ClCompile Include="..\Python\dtoa.c" />
|
<ClCompile Include="..\Python\dtoa.c" />
|
||||||
<ClCompile Include="..\Python\dynamic_annotations.c" />
|
<ClCompile Include="..\Python\dynamic_annotations.c" />
|
||||||
<ClCompile Include="..\Python\dynload_win.c" />
|
<ClCompile Include="..\Python\dynload_win.c" />
|
||||||
|
|
|
@ -103,6 +103,9 @@
|
||||||
<ClCompile Include="..\Python\context.c">
|
<ClCompile Include="..\Python\context.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\Python\crossinterp.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\Objects\descrobject.c">
|
<ClCompile Include="..\Objects\descrobject.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
|
@ -216,6 +216,7 @@
|
||||||
<ClInclude Include="..\Include\internal\pycore_complexobject.h" />
|
<ClInclude Include="..\Include\internal\pycore_complexobject.h" />
|
||||||
<ClInclude Include="..\Include\internal\pycore_condvar.h" />
|
<ClInclude Include="..\Include\internal\pycore_condvar.h" />
|
||||||
<ClInclude Include="..\Include\internal\pycore_context.h" />
|
<ClInclude Include="..\Include\internal\pycore_context.h" />
|
||||||
|
<ClInclude Include="..\Include\internal\pycore_crossinterp.h" />
|
||||||
<ClInclude Include="..\Include\internal\pycore_descrobject.h" />
|
<ClInclude Include="..\Include\internal\pycore_descrobject.h" />
|
||||||
<ClInclude Include="..\Include\internal\pycore_dict.h" />
|
<ClInclude Include="..\Include\internal\pycore_dict.h" />
|
||||||
<ClInclude Include="..\Include\internal\pycore_dict_state.h" />
|
<ClInclude Include="..\Include\internal\pycore_dict_state.h" />
|
||||||
|
@ -561,6 +562,7 @@
|
||||||
<ClCompile Include="..\Python\codecs.c" />
|
<ClCompile Include="..\Python\codecs.c" />
|
||||||
<ClCompile Include="..\Python\compile.c" />
|
<ClCompile Include="..\Python\compile.c" />
|
||||||
<ClCompile Include="..\Python\context.c" />
|
<ClCompile Include="..\Python\context.c" />
|
||||||
|
<ClCompile Include="..\Python\crossinterp.c" />
|
||||||
<ClCompile Include="..\Python\dynamic_annotations.c" />
|
<ClCompile Include="..\Python\dynamic_annotations.c" />
|
||||||
<ClCompile Include="..\Python\dynload_win.c" />
|
<ClCompile Include="..\Python\dynload_win.c" />
|
||||||
<ClCompile Include="..\Python\errors.c" />
|
<ClCompile Include="..\Python\errors.c" />
|
||||||
|
|
|
@ -579,6 +579,9 @@
|
||||||
<ClInclude Include="..\Include\internal\pycore_context.h">
|
<ClInclude Include="..\Include\internal\pycore_context.h">
|
||||||
<Filter>Include\internal</Filter>
|
<Filter>Include\internal</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\Include\internal\pycore_crossinterp.h">
|
||||||
|
<Filter>Include\internal</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\Include\internal\pycore_descrobject.h">
|
<ClInclude Include="..\Include\internal\pycore_descrobject.h">
|
||||||
<Filter>Include\internal</Filter>
|
<Filter>Include\internal</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -1280,6 +1283,9 @@
|
||||||
<ClCompile Include="..\Python\compile.c">
|
<ClCompile Include="..\Python\compile.c">
|
||||||
<Filter>Python</Filter>
|
<Filter>Python</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\Python\crossinterp.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\Python\dynamic_annotations.c">
|
<ClCompile Include="..\Python\dynamic_annotations.c">
|
||||||
<Filter>Python</Filter>
|
<Filter>Python</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
|
@ -0,0 +1,627 @@
|
||||||
|
|
||||||
|
/* API for managing interactions between isolated interpreters */
|
||||||
|
|
||||||
|
#include "Python.h"
|
||||||
|
#include "pycore_ceval.h" // _Py_simple_func
|
||||||
|
#include "pycore_crossinterp.h" // struct _xid
|
||||||
|
#include "pycore_pyerrors.h" // _PyErr_Clear()
|
||||||
|
#include "pycore_pystate.h" // _PyInterpreterState_GET()
|
||||||
|
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
|
||||||
|
|
||||||
|
|
||||||
|
/***************************/
|
||||||
|
/* cross-interpreter calls */
|
||||||
|
/***************************/
|
||||||
|
|
||||||
|
int
|
||||||
|
_Py_CallInInterpreter(PyInterpreterState *interp,
|
||||||
|
_Py_simple_func func, void *arg)
|
||||||
|
{
|
||||||
|
if (interp == _PyThreadState_GetCurrent()->interp) {
|
||||||
|
return func(arg);
|
||||||
|
}
|
||||||
|
// XXX Emit a warning if this fails?
|
||||||
|
_PyEval_AddPendingCall(interp, (_Py_pending_call_func)func, arg, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_Py_CallInInterpreterAndRawFree(PyInterpreterState *interp,
|
||||||
|
_Py_simple_func func, void *arg)
|
||||||
|
{
|
||||||
|
if (interp == _PyThreadState_GetCurrent()->interp) {
|
||||||
|
int res = func(arg);
|
||||||
|
PyMem_RawFree(arg);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
// XXX Emit a warning if this fails?
|
||||||
|
_PyEval_AddPendingCall(interp, func, arg, _Py_PENDING_RAWFREE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************/
|
||||||
|
/* cross-interpreter data */
|
||||||
|
/**************************/
|
||||||
|
|
||||||
|
_PyCrossInterpreterData *
|
||||||
|
_PyCrossInterpreterData_New(void)
|
||||||
|
{
|
||||||
|
_PyCrossInterpreterData *xid = PyMem_RawMalloc(
|
||||||
|
sizeof(_PyCrossInterpreterData));
|
||||||
|
if (xid == NULL) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
}
|
||||||
|
return xid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyCrossInterpreterData_Free(_PyCrossInterpreterData *xid)
|
||||||
|
{
|
||||||
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
||||||
|
_PyCrossInterpreterData_Clear(interp, xid);
|
||||||
|
PyMem_RawFree(xid);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* defining cross-interpreter data */
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_xidata_init(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
// If the value is being reused
|
||||||
|
// then _xidata_clear() should have been called already.
|
||||||
|
assert(data->data == NULL);
|
||||||
|
assert(data->obj == NULL);
|
||||||
|
*data = (_PyCrossInterpreterData){0};
|
||||||
|
data->interpid = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_xidata_clear(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
// _PyCrossInterpreterData only has two members that need to be
|
||||||
|
// cleaned up, if set: "data" must be freed and "obj" must be decref'ed.
|
||||||
|
// In both cases the original (owning) interpreter must be used,
|
||||||
|
// which is the caller's responsibility to ensure.
|
||||||
|
if (data->data != NULL) {
|
||||||
|
if (data->free != NULL) {
|
||||||
|
data->free(data->data);
|
||||||
|
}
|
||||||
|
data->data = NULL;
|
||||||
|
}
|
||||||
|
Py_CLEAR(data->obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyCrossInterpreterData_Init(_PyCrossInterpreterData *data,
|
||||||
|
PyInterpreterState *interp,
|
||||||
|
void *shared, PyObject *obj,
|
||||||
|
xid_newobjectfunc new_object)
|
||||||
|
{
|
||||||
|
assert(data != NULL);
|
||||||
|
assert(new_object != NULL);
|
||||||
|
_xidata_init(data);
|
||||||
|
data->data = shared;
|
||||||
|
if (obj != NULL) {
|
||||||
|
assert(interp != NULL);
|
||||||
|
// released in _PyCrossInterpreterData_Clear()
|
||||||
|
data->obj = Py_NewRef(obj);
|
||||||
|
}
|
||||||
|
// Ideally every object would know its owning interpreter.
|
||||||
|
// Until then, we have to rely on the caller to identify it
|
||||||
|
// (but we don't need it in all cases).
|
||||||
|
data->interpid = (interp != NULL) ? interp->id : -1;
|
||||||
|
data->new_object = new_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyCrossInterpreterData_InitWithSize(_PyCrossInterpreterData *data,
|
||||||
|
PyInterpreterState *interp,
|
||||||
|
const size_t size, PyObject *obj,
|
||||||
|
xid_newobjectfunc new_object)
|
||||||
|
{
|
||||||
|
assert(size > 0);
|
||||||
|
// For now we always free the shared data in the same interpreter
|
||||||
|
// where it was allocated, so the interpreter is required.
|
||||||
|
assert(interp != NULL);
|
||||||
|
_PyCrossInterpreterData_Init(data, interp, NULL, obj, new_object);
|
||||||
|
data->data = PyMem_RawMalloc(size);
|
||||||
|
if (data->data == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
data->free = PyMem_RawFree;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyCrossInterpreterData_Clear(PyInterpreterState *interp,
|
||||||
|
_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
assert(data != NULL);
|
||||||
|
// This must be called in the owning interpreter.
|
||||||
|
assert(interp == NULL
|
||||||
|
|| data->interpid == -1
|
||||||
|
|| data->interpid == interp->id);
|
||||||
|
_xidata_clear(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* using cross-interpreter data */
|
||||||
|
|
||||||
|
static int
|
||||||
|
_check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
// data->data can be anything, including NULL, so we don't check it.
|
||||||
|
|
||||||
|
// data->obj may be NULL, so we don't check it.
|
||||||
|
|
||||||
|
if (data->interpid < 0) {
|
||||||
|
_PyErr_SetString(tstate, PyExc_SystemError, "missing interp");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->new_object == NULL) {
|
||||||
|
_PyErr_SetString(tstate, PyExc_SystemError, "missing new_object func");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// data->free may be NULL, so we don't check it.
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
crossinterpdatafunc _PyCrossInterpreterData_Lookup(PyObject *);
|
||||||
|
|
||||||
|
/* This is a separate func from _PyCrossInterpreterData_Lookup in order
|
||||||
|
to keep the registry code separate. */
|
||||||
|
static crossinterpdatafunc
|
||||||
|
_lookup_getdata(PyObject *obj)
|
||||||
|
{
|
||||||
|
crossinterpdatafunc getdata = _PyCrossInterpreterData_Lookup(obj);
|
||||||
|
if (getdata == NULL && PyErr_Occurred() == 0)
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"%S does not support cross-interpreter data", obj);
|
||||||
|
return getdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyObject_CheckCrossInterpreterData(PyObject *obj)
|
||||||
|
{
|
||||||
|
crossinterpdatafunc getdata = _lookup_getdata(obj);
|
||||||
|
if (getdata == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
PyThreadState *tstate = _PyThreadState_GetCurrent();
|
||||||
|
#ifdef Py_DEBUG
|
||||||
|
// The caller must hold the GIL
|
||||||
|
_Py_EnsureTstateNotNULL(tstate);
|
||||||
|
#endif
|
||||||
|
PyInterpreterState *interp = tstate->interp;
|
||||||
|
|
||||||
|
// Reset data before re-populating.
|
||||||
|
*data = (_PyCrossInterpreterData){0};
|
||||||
|
data->interpid = -1;
|
||||||
|
|
||||||
|
// Call the "getdata" func for the object.
|
||||||
|
Py_INCREF(obj);
|
||||||
|
crossinterpdatafunc getdata = _lookup_getdata(obj);
|
||||||
|
if (getdata == NULL) {
|
||||||
|
Py_DECREF(obj);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int res = getdata(tstate, obj, data);
|
||||||
|
Py_DECREF(obj);
|
||||||
|
if (res != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the blanks and validate the result.
|
||||||
|
data->interpid = interp->id;
|
||||||
|
if (_check_xidata(tstate, data) != 0) {
|
||||||
|
(void)_PyCrossInterpreterData_Release(data);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
_PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
return data->new_object(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_call_clear_xidata(void *data)
|
||||||
|
{
|
||||||
|
_xidata_clear((_PyCrossInterpreterData *)data);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_xidata_release(_PyCrossInterpreterData *data, int rawfree)
|
||||||
|
{
|
||||||
|
if ((data->data == NULL || data->free == NULL) && data->obj == NULL) {
|
||||||
|
// Nothing to release!
|
||||||
|
if (rawfree) {
|
||||||
|
PyMem_RawFree(data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data->data = NULL;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to the original interpreter.
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_LookUpID(data->interpid);
|
||||||
|
if (interp == NULL) {
|
||||||
|
// The interpreter was already destroyed.
|
||||||
|
// This function shouldn't have been called.
|
||||||
|
// XXX Someone leaked some memory...
|
||||||
|
assert(PyErr_Occurred());
|
||||||
|
if (rawfree) {
|
||||||
|
PyMem_RawFree(data);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Release" the data and/or the object.
|
||||||
|
if (rawfree) {
|
||||||
|
return _Py_CallInInterpreterAndRawFree(interp, _call_clear_xidata, data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return _Py_CallInInterpreter(interp, _call_clear_xidata, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyCrossInterpreterData_Release(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
return _xidata_release(data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
return _xidata_release(data, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* registry of {type -> crossinterpdatafunc} */
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
|
||||||
|
static int
|
||||||
|
_xidregistry_add_type(struct _xidregistry *xidregistry,
|
||||||
|
PyTypeObject *cls, crossinterpdatafunc getdata)
|
||||||
|
{
|
||||||
|
struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem));
|
||||||
|
if (newhead == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*newhead = (struct _xidregitem){
|
||||||
|
// We do not keep a reference, to avoid keeping the class alive.
|
||||||
|
.cls = cls,
|
||||||
|
.refcount = 1,
|
||||||
|
.getdata = getdata,
|
||||||
|
};
|
||||||
|
if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
|
||||||
|
// XXX Assign a callback to clear the entry from the registry?
|
||||||
|
newhead->weakref = PyWeakref_NewRef((PyObject *)cls, NULL);
|
||||||
|
if (newhead->weakref == NULL) {
|
||||||
|
PyMem_RawFree(newhead);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newhead->next = xidregistry->head;
|
||||||
|
if (newhead->next != NULL) {
|
||||||
|
newhead->next->prev = newhead;
|
||||||
|
}
|
||||||
|
xidregistry->head = newhead;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct _xidregitem *
|
||||||
|
_xidregistry_remove_entry(struct _xidregistry *xidregistry,
|
||||||
|
struct _xidregitem *entry)
|
||||||
|
{
|
||||||
|
struct _xidregitem *next = entry->next;
|
||||||
|
if (entry->prev != NULL) {
|
||||||
|
assert(entry->prev->next == entry);
|
||||||
|
entry->prev->next = next;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(xidregistry->head == entry);
|
||||||
|
xidregistry->head = next;
|
||||||
|
}
|
||||||
|
if (next != NULL) {
|
||||||
|
next->prev = entry->prev;
|
||||||
|
}
|
||||||
|
Py_XDECREF(entry->weakref);
|
||||||
|
PyMem_RawFree(entry);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is used in pystate.c (for now).
|
||||||
|
void
|
||||||
|
_Py_xidregistry_clear(struct _xidregistry *xidregistry)
|
||||||
|
{
|
||||||
|
struct _xidregitem *cur = xidregistry->head;
|
||||||
|
xidregistry->head = NULL;
|
||||||
|
while (cur != NULL) {
|
||||||
|
struct _xidregitem *next = cur->next;
|
||||||
|
Py_XDECREF(cur->weakref);
|
||||||
|
PyMem_RawFree(cur);
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct _xidregitem *
|
||||||
|
_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
|
||||||
|
{
|
||||||
|
struct _xidregitem *cur = xidregistry->head;
|
||||||
|
while (cur != NULL) {
|
||||||
|
if (cur->weakref != NULL) {
|
||||||
|
// cur is/was a heap type.
|
||||||
|
PyObject *registered = _PyWeakref_GET_REF(cur->weakref);
|
||||||
|
if (registered == NULL) {
|
||||||
|
// The weakly ref'ed object was freed.
|
||||||
|
cur = _xidregistry_remove_entry(xidregistry, cur);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
assert(PyType_Check(registered));
|
||||||
|
assert(cur->cls == (PyTypeObject *)registered);
|
||||||
|
assert(cur->cls->tp_flags & Py_TPFLAGS_HEAPTYPE);
|
||||||
|
Py_DECREF(registered);
|
||||||
|
}
|
||||||
|
if (cur->cls == cls) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct _xidregistry *
|
||||||
|
_get_xidregistry(PyInterpreterState *interp, PyTypeObject *cls)
|
||||||
|
{
|
||||||
|
struct _xidregistry *xidregistry = &interp->runtime->xidregistry;
|
||||||
|
if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
|
||||||
|
assert(interp->xidregistry.mutex == xidregistry->mutex);
|
||||||
|
xidregistry = &interp->xidregistry;
|
||||||
|
}
|
||||||
|
return xidregistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_ensure_builtins_xid(PyInterpreterState *interp, struct _xidregistry *xidregistry)
|
||||||
|
{
|
||||||
|
if (xidregistry != &interp->xidregistry) {
|
||||||
|
assert(xidregistry == &interp->runtime->xidregistry);
|
||||||
|
if (xidregistry->head == NULL) {
|
||||||
|
_register_builtins_for_crossinterpreter_data(xidregistry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
|
||||||
|
crossinterpdatafunc getdata)
|
||||||
|
{
|
||||||
|
if (!PyType_Check(cls)) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "only classes may be registered");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (getdata == NULL) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "missing 'getdata' func");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
|
||||||
|
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
|
||||||
|
|
||||||
|
_ensure_builtins_xid(interp, xidregistry);
|
||||||
|
|
||||||
|
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
|
||||||
|
if (matched != NULL) {
|
||||||
|
assert(matched->getdata == getdata);
|
||||||
|
matched->refcount += 1;
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = _xidregistry_add_type(xidregistry, cls, getdata);
|
||||||
|
|
||||||
|
finally:
|
||||||
|
PyThread_release_lock(xidregistry->mutex);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
|
||||||
|
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
|
||||||
|
|
||||||
|
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
|
||||||
|
if (matched != NULL) {
|
||||||
|
assert(matched->refcount > 0);
|
||||||
|
matched->refcount -= 1;
|
||||||
|
if (matched->refcount == 0) {
|
||||||
|
(void)_xidregistry_remove_entry(xidregistry, matched);
|
||||||
|
}
|
||||||
|
res = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyThread_release_lock(xidregistry->mutex);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Cross-interpreter objects are looked up by exact match on the class.
|
||||||
|
We can reassess this policy when we move from a global registry to a
|
||||||
|
tp_* slot. */
|
||||||
|
|
||||||
|
crossinterpdatafunc
|
||||||
|
_PyCrossInterpreterData_Lookup(PyObject *obj)
|
||||||
|
{
|
||||||
|
PyTypeObject *cls = Py_TYPE(obj);
|
||||||
|
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
|
||||||
|
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
|
||||||
|
|
||||||
|
_ensure_builtins_xid(interp, xidregistry);
|
||||||
|
|
||||||
|
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
|
||||||
|
crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL;
|
||||||
|
|
||||||
|
PyThread_release_lock(xidregistry->mutex);
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cross-interpreter data for builtin types */
|
||||||
|
|
||||||
|
struct _shared_bytes_data {
|
||||||
|
char *bytes;
|
||||||
|
Py_ssize_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_new_bytes_object(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(data->data);
|
||||||
|
return PyBytes_FromStringAndSize(shared->bytes, shared->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_bytes_shared(PyThreadState *tstate, PyObject *obj,
|
||||||
|
_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
if (_PyCrossInterpreterData_InitWithSize(
|
||||||
|
data, tstate->interp, sizeof(struct _shared_bytes_data), obj,
|
||||||
|
_new_bytes_object
|
||||||
|
) < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
struct _shared_bytes_data *shared = (struct _shared_bytes_data *)data->data;
|
||||||
|
if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) {
|
||||||
|
_PyCrossInterpreterData_Clear(tstate->interp, data);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _shared_str_data {
|
||||||
|
int kind;
|
||||||
|
const void *buffer;
|
||||||
|
Py_ssize_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_new_str_object(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
struct _shared_str_data *shared = (struct _shared_str_data *)(data->data);
|
||||||
|
return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_str_shared(PyThreadState *tstate, PyObject *obj,
|
||||||
|
_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
if (_PyCrossInterpreterData_InitWithSize(
|
||||||
|
data, tstate->interp, sizeof(struct _shared_str_data), obj,
|
||||||
|
_new_str_object
|
||||||
|
) < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
struct _shared_str_data *shared = (struct _shared_str_data *)data->data;
|
||||||
|
shared->kind = PyUnicode_KIND(obj);
|
||||||
|
shared->buffer = PyUnicode_DATA(obj);
|
||||||
|
shared->len = PyUnicode_GET_LENGTH(obj);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_new_long_object(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
return PyLong_FromSsize_t((Py_ssize_t)(data->data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_long_shared(PyThreadState *tstate, PyObject *obj,
|
||||||
|
_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
/* Note that this means the size of shareable ints is bounded by
|
||||||
|
* sys.maxsize. Hence on 32-bit architectures that is half the
|
||||||
|
* size of maximum shareable ints on 64-bit.
|
||||||
|
*/
|
||||||
|
Py_ssize_t value = PyLong_AsSsize_t(obj);
|
||||||
|
if (value == -1 && PyErr_Occurred()) {
|
||||||
|
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
|
||||||
|
PyErr_SetString(PyExc_OverflowError, "try sending as bytes");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
_PyCrossInterpreterData_Init(data, tstate->interp, (void *)value, NULL,
|
||||||
|
_new_long_object);
|
||||||
|
// data->obj and data->free remain NULL
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_new_none_object(_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
// XXX Singleton refcounts are problematic across interpreters...
|
||||||
|
return Py_NewRef(Py_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_none_shared(PyThreadState *tstate, PyObject *obj,
|
||||||
|
_PyCrossInterpreterData *data)
|
||||||
|
{
|
||||||
|
_PyCrossInterpreterData_Init(data, tstate->interp, NULL, NULL,
|
||||||
|
_new_none_object);
|
||||||
|
// data->data, data->obj and data->free remain NULL
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry)
|
||||||
|
{
|
||||||
|
// None
|
||||||
|
if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) {
|
||||||
|
Py_FatalError("could not register None for cross-interpreter sharing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// int
|
||||||
|
if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) {
|
||||||
|
Py_FatalError("could not register int for cross-interpreter sharing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytes
|
||||||
|
if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _bytes_shared) != 0) {
|
||||||
|
Py_FatalError("could not register bytes for cross-interpreter sharing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// str
|
||||||
|
if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) {
|
||||||
|
Py_FatalError("could not register str for cross-interpreter sharing");
|
||||||
|
}
|
||||||
|
}
|
595
Python/pystate.c
595
Python/pystate.c
|
@ -16,7 +16,6 @@
|
||||||
#include "pycore_pystate.h"
|
#include "pycore_pystate.h"
|
||||||
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
|
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
|
||||||
#include "pycore_sysmodule.h" // _PySys_Audit()
|
#include "pycore_sysmodule.h" // _PySys_Audit()
|
||||||
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
CAUTION
|
CAUTION
|
||||||
|
@ -495,7 +494,8 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
|
||||||
return _PyStatus_OK();
|
return _PyStatus_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _xidregistry_clear(struct _xidregistry *);
|
// This is defined in crossinterp.c (for now).
|
||||||
|
extern void _Py_xidregistry_clear(struct _xidregistry *);
|
||||||
|
|
||||||
void
|
void
|
||||||
_PyRuntimeState_Fini(_PyRuntimeState *runtime)
|
_PyRuntimeState_Fini(_PyRuntimeState *runtime)
|
||||||
|
@ -505,7 +505,7 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
|
||||||
assert(runtime->object_state.interpreter_leaks == 0);
|
assert(runtime->object_state.interpreter_leaks == 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_xidregistry_clear(&runtime->xidregistry);
|
_Py_xidregistry_clear(&runtime->xidregistry);
|
||||||
|
|
||||||
if (gilstate_tss_initialized(runtime)) {
|
if (gilstate_tss_initialized(runtime)) {
|
||||||
gilstate_tss_fini(runtime);
|
gilstate_tss_fini(runtime);
|
||||||
|
@ -948,7 +948,7 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
|
||||||
Py_CLEAR(interp->sysdict);
|
Py_CLEAR(interp->sysdict);
|
||||||
Py_CLEAR(interp->builtins);
|
Py_CLEAR(interp->builtins);
|
||||||
|
|
||||||
_xidregistry_clear(&interp->xidregistry);
|
_Py_xidregistry_clear(&interp->xidregistry);
|
||||||
/* The lock is owned by the runtime, so we don't free it here. */
|
/* The lock is owned by the runtime, so we don't free it here. */
|
||||||
interp->xidregistry.mutex = NULL;
|
interp->xidregistry.mutex = NULL;
|
||||||
|
|
||||||
|
@ -2415,593 +2415,6 @@ PyGILState_Release(PyGILState_STATE oldstate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**************************/
|
|
||||||
/* cross-interpreter data */
|
|
||||||
/**************************/
|
|
||||||
|
|
||||||
/* cross-interpreter data */
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
_xidata_init(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
// If the value is being reused
|
|
||||||
// then _xidata_clear() should have been called already.
|
|
||||||
assert(data->data == NULL);
|
|
||||||
assert(data->obj == NULL);
|
|
||||||
*data = (_PyCrossInterpreterData){0};
|
|
||||||
data->interpid = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
_xidata_clear(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
// _PyCrossInterpreterData only has two members that need to be
|
|
||||||
// cleaned up, if set: "data" must be freed and "obj" must be decref'ed.
|
|
||||||
// In both cases the original (owning) interpreter must be used,
|
|
||||||
// which is the caller's responsibility to ensure.
|
|
||||||
if (data->data != NULL) {
|
|
||||||
if (data->free != NULL) {
|
|
||||||
data->free(data->data);
|
|
||||||
}
|
|
||||||
data->data = NULL;
|
|
||||||
}
|
|
||||||
Py_CLEAR(data->obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
_PyCrossInterpreterData_Init(_PyCrossInterpreterData *data,
|
|
||||||
PyInterpreterState *interp,
|
|
||||||
void *shared, PyObject *obj,
|
|
||||||
xid_newobjectfunc new_object)
|
|
||||||
{
|
|
||||||
assert(data != NULL);
|
|
||||||
assert(new_object != NULL);
|
|
||||||
_xidata_init(data);
|
|
||||||
data->data = shared;
|
|
||||||
if (obj != NULL) {
|
|
||||||
assert(interp != NULL);
|
|
||||||
// released in _PyCrossInterpreterData_Clear()
|
|
||||||
data->obj = Py_NewRef(obj);
|
|
||||||
}
|
|
||||||
// Ideally every object would know its owning interpreter.
|
|
||||||
// Until then, we have to rely on the caller to identify it
|
|
||||||
// (but we don't need it in all cases).
|
|
||||||
data->interpid = (interp != NULL) ? interp->id : -1;
|
|
||||||
data->new_object = new_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyCrossInterpreterData_InitWithSize(_PyCrossInterpreterData *data,
|
|
||||||
PyInterpreterState *interp,
|
|
||||||
const size_t size, PyObject *obj,
|
|
||||||
xid_newobjectfunc new_object)
|
|
||||||
{
|
|
||||||
assert(size > 0);
|
|
||||||
// For now we always free the shared data in the same interpreter
|
|
||||||
// where it was allocated, so the interpreter is required.
|
|
||||||
assert(interp != NULL);
|
|
||||||
_PyCrossInterpreterData_Init(data, interp, NULL, obj, new_object);
|
|
||||||
data->data = PyMem_RawMalloc(size);
|
|
||||||
if (data->data == NULL) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
data->free = PyMem_RawFree;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
_PyCrossInterpreterData_Clear(PyInterpreterState *interp,
|
|
||||||
_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
assert(data != NULL);
|
|
||||||
// This must be called in the owning interpreter.
|
|
||||||
assert(interp == NULL || data->interpid == interp->id);
|
|
||||||
_xidata_clear(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
// data->data can be anything, including NULL, so we don't check it.
|
|
||||||
|
|
||||||
// data->obj may be NULL, so we don't check it.
|
|
||||||
|
|
||||||
if (data->interpid < 0) {
|
|
||||||
_PyErr_SetString(tstate, PyExc_SystemError, "missing interp");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data->new_object == NULL) {
|
|
||||||
_PyErr_SetString(tstate, PyExc_SystemError, "missing new_object func");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// data->free may be NULL, so we don't check it.
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
crossinterpdatafunc _PyCrossInterpreterData_Lookup(PyObject *);
|
|
||||||
|
|
||||||
/* This is a separate func from _PyCrossInterpreterData_Lookup in order
|
|
||||||
to keep the registry code separate. */
|
|
||||||
static crossinterpdatafunc
|
|
||||||
_lookup_getdata(PyObject *obj)
|
|
||||||
{
|
|
||||||
crossinterpdatafunc getdata = _PyCrossInterpreterData_Lookup(obj);
|
|
||||||
if (getdata == NULL && PyErr_Occurred() == 0)
|
|
||||||
PyErr_Format(PyExc_ValueError,
|
|
||||||
"%S does not support cross-interpreter data", obj);
|
|
||||||
return getdata;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyObject_CheckCrossInterpreterData(PyObject *obj)
|
|
||||||
{
|
|
||||||
crossinterpdatafunc getdata = _lookup_getdata(obj);
|
|
||||||
if (getdata == NULL) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
_PyRuntimeState *runtime = &_PyRuntime;
|
|
||||||
PyThreadState *tstate = current_fast_get(runtime);
|
|
||||||
#ifdef Py_DEBUG
|
|
||||||
// The caller must hold the GIL
|
|
||||||
_Py_EnsureTstateNotNULL(tstate);
|
|
||||||
#endif
|
|
||||||
PyInterpreterState *interp = tstate->interp;
|
|
||||||
|
|
||||||
// Reset data before re-populating.
|
|
||||||
*data = (_PyCrossInterpreterData){0};
|
|
||||||
data->interpid = -1;
|
|
||||||
|
|
||||||
// Call the "getdata" func for the object.
|
|
||||||
Py_INCREF(obj);
|
|
||||||
crossinterpdatafunc getdata = _lookup_getdata(obj);
|
|
||||||
if (getdata == NULL) {
|
|
||||||
Py_DECREF(obj);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int res = getdata(tstate, obj, data);
|
|
||||||
Py_DECREF(obj);
|
|
||||||
if (res != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill in the blanks and validate the result.
|
|
||||||
data->interpid = interp->id;
|
|
||||||
if (_check_xidata(tstate, data) != 0) {
|
|
||||||
(void)_PyCrossInterpreterData_Release(data);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject *
|
|
||||||
_PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
return data->new_object(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_Py_CallInInterpreter(PyInterpreterState *interp,
|
|
||||||
_Py_simple_func func, void *arg)
|
|
||||||
{
|
|
||||||
if (interp == current_fast_get(interp->runtime)->interp) {
|
|
||||||
return func(arg);
|
|
||||||
}
|
|
||||||
// XXX Emit a warning if this fails?
|
|
||||||
_PyEval_AddPendingCall(interp, (_Py_pending_call_func)func, arg, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_Py_CallInInterpreterAndRawFree(PyInterpreterState *interp,
|
|
||||||
_Py_simple_func func, void *arg)
|
|
||||||
{
|
|
||||||
if (interp == current_fast_get(interp->runtime)->interp) {
|
|
||||||
int res = func(arg);
|
|
||||||
PyMem_RawFree(arg);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
// XXX Emit a warning if this fails?
|
|
||||||
_PyEval_AddPendingCall(interp, func, arg, _Py_PENDING_RAWFREE);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_call_clear_xidata(void *data)
|
|
||||||
{
|
|
||||||
_xidata_clear((_PyCrossInterpreterData *)data);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_xidata_release(_PyCrossInterpreterData *data, int rawfree)
|
|
||||||
{
|
|
||||||
if ((data->data == NULL || data->free == NULL) && data->obj == NULL) {
|
|
||||||
// Nothing to release!
|
|
||||||
if (rawfree) {
|
|
||||||
PyMem_RawFree(data);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
data->data = NULL;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to the original interpreter.
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_LookUpID(data->interpid);
|
|
||||||
if (interp == NULL) {
|
|
||||||
// The interpreter was already destroyed.
|
|
||||||
// This function shouldn't have been called.
|
|
||||||
// XXX Someone leaked some memory...
|
|
||||||
assert(PyErr_Occurred());
|
|
||||||
if (rawfree) {
|
|
||||||
PyMem_RawFree(data);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Release" the data and/or the object.
|
|
||||||
if (rawfree) {
|
|
||||||
return _Py_CallInInterpreterAndRawFree(interp, _call_clear_xidata, data);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return _Py_CallInInterpreter(interp, _call_clear_xidata, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyCrossInterpreterData_Release(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
return _xidata_release(data, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
return _xidata_release(data, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* registry of {type -> crossinterpdatafunc} */
|
|
||||||
|
|
||||||
/* 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. */
|
|
||||||
|
|
||||||
static int
|
|
||||||
_xidregistry_add_type(struct _xidregistry *xidregistry,
|
|
||||||
PyTypeObject *cls, crossinterpdatafunc getdata)
|
|
||||||
{
|
|
||||||
struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem));
|
|
||||||
if (newhead == NULL) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*newhead = (struct _xidregitem){
|
|
||||||
// We do not keep a reference, to avoid keeping the class alive.
|
|
||||||
.cls = cls,
|
|
||||||
.refcount = 1,
|
|
||||||
.getdata = getdata,
|
|
||||||
};
|
|
||||||
if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
|
|
||||||
// XXX Assign a callback to clear the entry from the registry?
|
|
||||||
newhead->weakref = PyWeakref_NewRef((PyObject *)cls, NULL);
|
|
||||||
if (newhead->weakref == NULL) {
|
|
||||||
PyMem_RawFree(newhead);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newhead->next = xidregistry->head;
|
|
||||||
if (newhead->next != NULL) {
|
|
||||||
newhead->next->prev = newhead;
|
|
||||||
}
|
|
||||||
xidregistry->head = newhead;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct _xidregitem *
|
|
||||||
_xidregistry_remove_entry(struct _xidregistry *xidregistry,
|
|
||||||
struct _xidregitem *entry)
|
|
||||||
{
|
|
||||||
struct _xidregitem *next = entry->next;
|
|
||||||
if (entry->prev != NULL) {
|
|
||||||
assert(entry->prev->next == entry);
|
|
||||||
entry->prev->next = next;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
assert(xidregistry->head == entry);
|
|
||||||
xidregistry->head = next;
|
|
||||||
}
|
|
||||||
if (next != NULL) {
|
|
||||||
next->prev = entry->prev;
|
|
||||||
}
|
|
||||||
Py_XDECREF(entry->weakref);
|
|
||||||
PyMem_RawFree(entry);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_xidregistry_clear(struct _xidregistry *xidregistry)
|
|
||||||
{
|
|
||||||
struct _xidregitem *cur = xidregistry->head;
|
|
||||||
xidregistry->head = NULL;
|
|
||||||
while (cur != NULL) {
|
|
||||||
struct _xidregitem *next = cur->next;
|
|
||||||
Py_XDECREF(cur->weakref);
|
|
||||||
PyMem_RawFree(cur);
|
|
||||||
cur = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct _xidregitem *
|
|
||||||
_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
|
|
||||||
{
|
|
||||||
struct _xidregitem *cur = xidregistry->head;
|
|
||||||
while (cur != NULL) {
|
|
||||||
if (cur->weakref != NULL) {
|
|
||||||
// cur is/was a heap type.
|
|
||||||
PyObject *registered = _PyWeakref_GET_REF(cur->weakref);
|
|
||||||
if (registered == NULL) {
|
|
||||||
// The weakly ref'ed object was freed.
|
|
||||||
cur = _xidregistry_remove_entry(xidregistry, cur);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
assert(PyType_Check(registered));
|
|
||||||
assert(cur->cls == (PyTypeObject *)registered);
|
|
||||||
assert(cur->cls->tp_flags & Py_TPFLAGS_HEAPTYPE);
|
|
||||||
Py_DECREF(registered);
|
|
||||||
}
|
|
||||||
if (cur->cls == cls) {
|
|
||||||
return cur;
|
|
||||||
}
|
|
||||||
cur = cur->next;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline struct _xidregistry *
|
|
||||||
_get_xidregistry(PyInterpreterState *interp, PyTypeObject *cls)
|
|
||||||
{
|
|
||||||
struct _xidregistry *xidregistry = &interp->runtime->xidregistry;
|
|
||||||
if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
|
|
||||||
assert(interp->xidregistry.mutex == xidregistry->mutex);
|
|
||||||
xidregistry = &interp->xidregistry;
|
|
||||||
}
|
|
||||||
return xidregistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry);
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
_ensure_builtins_xid(PyInterpreterState *interp, struct _xidregistry *xidregistry)
|
|
||||||
{
|
|
||||||
if (xidregistry != &interp->xidregistry) {
|
|
||||||
assert(xidregistry == &interp->runtime->xidregistry);
|
|
||||||
if (xidregistry->head == NULL) {
|
|
||||||
_register_builtins_for_crossinterpreter_data(xidregistry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
|
|
||||||
crossinterpdatafunc getdata)
|
|
||||||
{
|
|
||||||
if (!PyType_Check(cls)) {
|
|
||||||
PyErr_Format(PyExc_ValueError, "only classes may be registered");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (getdata == NULL) {
|
|
||||||
PyErr_Format(PyExc_ValueError, "missing 'getdata' func");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int res = 0;
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
|
||||||
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
|
|
||||||
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
|
|
||||||
|
|
||||||
_ensure_builtins_xid(interp, xidregistry);
|
|
||||||
|
|
||||||
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
|
|
||||||
if (matched != NULL) {
|
|
||||||
assert(matched->getdata == getdata);
|
|
||||||
matched->refcount += 1;
|
|
||||||
goto finally;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = _xidregistry_add_type(xidregistry, cls, getdata);
|
|
||||||
|
|
||||||
finally:
|
|
||||||
PyThread_release_lock(xidregistry->mutex);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
|
|
||||||
{
|
|
||||||
int res = 0;
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
|
||||||
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
|
|
||||||
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
|
|
||||||
|
|
||||||
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
|
|
||||||
if (matched != NULL) {
|
|
||||||
assert(matched->refcount > 0);
|
|
||||||
matched->refcount -= 1;
|
|
||||||
if (matched->refcount == 0) {
|
|
||||||
(void)_xidregistry_remove_entry(xidregistry, matched);
|
|
||||||
}
|
|
||||||
res = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyThread_release_lock(xidregistry->mutex);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Cross-interpreter objects are looked up by exact match on the class.
|
|
||||||
We can reassess this policy when we move from a global registry to a
|
|
||||||
tp_* slot. */
|
|
||||||
|
|
||||||
crossinterpdatafunc
|
|
||||||
_PyCrossInterpreterData_Lookup(PyObject *obj)
|
|
||||||
{
|
|
||||||
PyTypeObject *cls = Py_TYPE(obj);
|
|
||||||
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
|
||||||
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
|
|
||||||
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
|
|
||||||
|
|
||||||
_ensure_builtins_xid(interp, xidregistry);
|
|
||||||
|
|
||||||
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
|
|
||||||
crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL;
|
|
||||||
|
|
||||||
PyThread_release_lock(xidregistry->mutex);
|
|
||||||
return func;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* cross-interpreter data for builtin types */
|
|
||||||
|
|
||||||
struct _shared_bytes_data {
|
|
||||||
char *bytes;
|
|
||||||
Py_ssize_t len;
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
_new_bytes_object(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(data->data);
|
|
||||||
return PyBytes_FromStringAndSize(shared->bytes, shared->len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_bytes_shared(PyThreadState *tstate, PyObject *obj,
|
|
||||||
_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
if (_PyCrossInterpreterData_InitWithSize(
|
|
||||||
data, tstate->interp, sizeof(struct _shared_bytes_data), obj,
|
|
||||||
_new_bytes_object
|
|
||||||
) < 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
struct _shared_bytes_data *shared = (struct _shared_bytes_data *)data->data;
|
|
||||||
if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) {
|
|
||||||
_PyCrossInterpreterData_Clear(tstate->interp, data);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct _shared_str_data {
|
|
||||||
int kind;
|
|
||||||
const void *buffer;
|
|
||||||
Py_ssize_t len;
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
_new_str_object(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
struct _shared_str_data *shared = (struct _shared_str_data *)(data->data);
|
|
||||||
return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_str_shared(PyThreadState *tstate, PyObject *obj,
|
|
||||||
_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
if (_PyCrossInterpreterData_InitWithSize(
|
|
||||||
data, tstate->interp, sizeof(struct _shared_str_data), obj,
|
|
||||||
_new_str_object
|
|
||||||
) < 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
struct _shared_str_data *shared = (struct _shared_str_data *)data->data;
|
|
||||||
shared->kind = PyUnicode_KIND(obj);
|
|
||||||
shared->buffer = PyUnicode_DATA(obj);
|
|
||||||
shared->len = PyUnicode_GET_LENGTH(obj);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
_new_long_object(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
return PyLong_FromSsize_t((Py_ssize_t)(data->data));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_long_shared(PyThreadState *tstate, PyObject *obj,
|
|
||||||
_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
/* Note that this means the size of shareable ints is bounded by
|
|
||||||
* sys.maxsize. Hence on 32-bit architectures that is half the
|
|
||||||
* size of maximum shareable ints on 64-bit.
|
|
||||||
*/
|
|
||||||
Py_ssize_t value = PyLong_AsSsize_t(obj);
|
|
||||||
if (value == -1 && PyErr_Occurred()) {
|
|
||||||
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
|
|
||||||
PyErr_SetString(PyExc_OverflowError, "try sending as bytes");
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
_PyCrossInterpreterData_Init(data, tstate->interp, (void *)value, NULL,
|
|
||||||
_new_long_object);
|
|
||||||
// data->obj and data->free remain NULL
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
_new_none_object(_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
// XXX Singleton refcounts are problematic across interpreters...
|
|
||||||
return Py_NewRef(Py_None);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_none_shared(PyThreadState *tstate, PyObject *obj,
|
|
||||||
_PyCrossInterpreterData *data)
|
|
||||||
{
|
|
||||||
_PyCrossInterpreterData_Init(data, tstate->interp, NULL, NULL,
|
|
||||||
_new_none_object);
|
|
||||||
// data->data, data->obj and data->free remain NULL
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry)
|
|
||||||
{
|
|
||||||
// None
|
|
||||||
if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) {
|
|
||||||
Py_FatalError("could not register None for cross-interpreter sharing");
|
|
||||||
}
|
|
||||||
|
|
||||||
// int
|
|
||||||
if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) {
|
|
||||||
Py_FatalError("could not register int for cross-interpreter sharing");
|
|
||||||
}
|
|
||||||
|
|
||||||
// bytes
|
|
||||||
if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _bytes_shared) != 0) {
|
|
||||||
Py_FatalError("could not register bytes for cross-interpreter sharing");
|
|
||||||
}
|
|
||||||
|
|
||||||
// str
|
|
||||||
if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) {
|
|
||||||
Py_FatalError("could not register str for cross-interpreter sharing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*************/
|
/*************/
|
||||||
/* Other API */
|
/* Other API */
|
||||||
/*************/
|
/*************/
|
||||||
|
|
Loading…
Reference in New Issue