This change consists of adding tests and moving code around, with some renaming thrown in.
// likely a registered "xidatafunc", is responsible for
// ensuring it owns the reference (i.e. incref).
PyObject *obj;
- // interp is the ID of the owning interpreter of the original
+ // interpid is the ID of the owning interpreter of the original
// object. It corresponds to the active interpreter when
// _PyObject_GetXIData() was called. This should only
// be set by the cross-interpreter machinery.
// Users should not need getters for "new_object" or "free".
-/* getting cross-interpreter data */
-
-typedef int (*xidatafunc)(PyThreadState *tstate, PyObject *, _PyXIData_t *);
-
-PyAPI_FUNC(PyObject *) _PyXIData_GetNotShareableErrorType(PyThreadState *);
-PyAPI_FUNC(void) _PyXIData_SetNotShareableError(PyThreadState *, const char *);
-PyAPI_FUNC(void) _PyXIData_FormatNotShareableError(
- PyThreadState *,
- const char *,
- ...);
-
-PyAPI_FUNC(xidatafunc) _PyXIData_Lookup(
- PyThreadState *,
- PyObject *);
-PyAPI_FUNC(int) _PyObject_CheckXIData(
- PyThreadState *,
- PyObject *);
-
-PyAPI_FUNC(int) _PyObject_GetXIData(
- PyThreadState *,
- PyObject *,
- _PyXIData_t *);
-
-
-/* using cross-interpreter data */
-
-PyAPI_FUNC(PyObject *) _PyXIData_NewObject(_PyXIData_t *);
-PyAPI_FUNC(int) _PyXIData_Release(_PyXIData_t *);
-PyAPI_FUNC(int) _PyXIData_ReleaseAndRawFree(_PyXIData_t *);
-
-
/* defining cross-interpreter data */
PyAPI_FUNC(void) _PyXIData_Init(
_PyXIData_t *,
PyInterpreterState *interp, const size_t, PyObject *,
xid_newobjfunc);
-PyAPI_FUNC(void) _PyXIData_Clear( PyInterpreterState *, _PyXIData_t *);
+PyAPI_FUNC(void) _PyXIData_Clear(PyInterpreterState *, _PyXIData_t *);
// Normally the Init* functions are sufficient. The only time
// additional initialization might be needed is to set the "free" func,
} while (0)
+/* getting cross-interpreter data */
+
+typedef int (*xidatafunc)(PyThreadState *tstate, PyObject *, _PyXIData_t *);
+
+PyAPI_FUNC(PyObject *) _PyXIData_GetNotShareableErrorType(PyThreadState *);
+PyAPI_FUNC(void) _PyXIData_SetNotShareableError(PyThreadState *, const char *);
+PyAPI_FUNC(void) _PyXIData_FormatNotShareableError(
+ PyThreadState *,
+ const char *,
+ ...);
+
+PyAPI_FUNC(xidatafunc) _PyXIData_Lookup(
+ PyThreadState *,
+ PyObject *);
+PyAPI_FUNC(int) _PyObject_CheckXIData(
+ PyThreadState *,
+ PyObject *);
+
+PyAPI_FUNC(int) _PyObject_GetXIData(
+ PyThreadState *,
+ PyObject *,
+ _PyXIData_t *);
+
+
+/* using cross-interpreter data */
+
+PyAPI_FUNC(PyObject *) _PyXIData_NewObject(_PyXIData_t *);
+PyAPI_FUNC(int) _PyXIData_Release(_PyXIData_t *);
+PyAPI_FUNC(int) _PyXIData_ReleaseAndRawFree(_PyXIData_t *);
+
+
/* cross-interpreter data registry */
#define Py_CORE_CROSSINTERP_DATA_REGISTRY_H
--- /dev/null
+# This may be loaded as a module, in the current __main__ module,
+# or in another __main__ module.
+
+
+#######################################
+# functions
+
+def spam_minimal():
+ # no arg defaults or kwarg defaults
+ # no annotations
+ # no local vars
+ # no free vars
+ # no globals
+ # no builtins
+ # no attr access (names)
+ # no code
+ return
+
+
+def spam_full(a, b, /, c, d:int=1, *args, e, f:object=None, **kwargs) -> tuple:
+ # arg defaults, kwarg defaults
+ # annotations
+ # all kinds of local vars, except cells
+ # no free vars
+ # some globals
+ # some builtins
+ # some attr access (names)
+ x = args
+ y = kwargs
+ z = (a, b, c, d)
+ kwargs['e'] = e
+ kwargs['f'] = f
+ extras = list((x, y, z, spam, spam.__name__))
+ return tuple(a, b, c, d, e, f, args, kwargs), extras
+
+
+def spam(x):
+ return x, None
+
+
+def spam_N(x):
+ def eggs_nested(y):
+ return None, y
+ return eggs_nested, x
+
+
+def spam_C(x):
+ a = 1
+ def eggs_closure(y):
+ return None, y, a, x
+ return eggs_closure, a, x
+
+
+def spam_NN(x):
+ def eggs_nested_N(y):
+ def ham_nested(z):
+ return None, z
+ return ham_nested, y
+ return eggs_nested_N, x
+
+
+def spam_NC(x):
+ a = 1
+ def eggs_nested_C(y):
+ def ham_closure(z):
+ return None, z, y, a, x
+ return ham_closure, y
+ return eggs_nested_C, a, x
+
+
+def spam_CN(x):
+ a = 1
+ def eggs_closure_N(y):
+ def ham_C_nested(z):
+ return None, z
+ return ham_C_nested, y, a, x
+ return eggs_closure_N, a, x
+
+
+def spam_CC(x):
+ a = 1
+ def eggs_closure_C(y):
+ b = 2
+ def ham_C_closure(z):
+ return None, z, b, y, a, x
+ return ham_C_closure, b, y, a, x
+ return eggs_closure_N, a, x
+
+
+eggs_nested, *_ = spam_N(1)
+eggs_closure, *_ = spam_C(1)
+eggs_nested_N, *_ = spam_NN(1)
+eggs_nested_C, *_ = spam_NC(1)
+eggs_closure_N, *_ = spam_CN(1)
+eggs_closure_C, *_ = spam_CC(1)
+
+ham_nested, *_ = eggs_nested_N(2)
+ham_closure, *_ = eggs_nested_C(2)
+ham_C_nested, *_ = eggs_closure_N(2)
+ham_C_closure, *_ = eggs_closure_C(2)
+
+
+FUNCTIONS = [
+ # shallow
+ spam_minimal,
+ spam_full,
+ spam,
+ # outer func
+ spam_N,
+ spam_C,
+ spam_NN,
+ spam_NC,
+ spam_CN,
+ spam_CC,
+ # inner func
+ eggs_nested,
+ eggs_closure,
+ eggs_nested_N,
+ eggs_nested_C,
+ eggs_closure_N,
+ eggs_closure_C,
+ # inner inner func
+ ham_nested,
+ ham_closure,
+ ham_C_nested,
+ ham_C_closure,
+]
+
+
+#######################################
+# function-like
+
+# generators
+
+def gen_spam_1(*args):
+ for arg in args:
+ yield arg
+
+
+def gen_spam_2(*args):
+ yield from args
+
+
+async def async_spam():
+ pass
+coro_spam = async_spam()
+coro_spam.close()
+
+
+async def asyncgen_spam(*args):
+ for arg in args:
+ yield arg
+asynccoro_spam = asyncgen_spam(1, 2, 3)
+
+
+FUNCTION_LIKE = [
+ gen_spam_1,
+ gen_spam_2,
+ async_spam,
+ coro_spam, # actually FunctionType?
+ asyncgen_spam,
+ asynccoro_spam, # actually FunctionType?
+]
+
+
+#######################################
+# classes
+
+class Spam:
+ # minimal
+ pass
+
+
+class SpamOkay:
+ def okay(self):
+ return True
+
+
+class SpamFull:
+
+ a: object
+ b: object
+ c: object
+
+ @staticmethod
+ def staticmeth(cls):
+ return True
+
+ @classmethod
+ def classmeth(cls):
+ return True
+
+ def __new__(cls, *args, **kwargs):
+ return super().__new__(cls)
+
+ def __init__(self, a, b, c):
+ self.a = a
+ self.b = b
+ self.c = c
+
+ # __repr__
+ # __str__
+ # ...
+
+ @property
+ def prop(self):
+ return True
+
+
+class SubSpamFull(SpamFull):
+ ...
+
+
+class SubTuple(tuple):
+ ...
+
+
+def class_eggs_inner():
+ class EggsNested:
+ ...
+ return EggsNested
+EggsNested = class_eggs_inner()
+
+
+
+#######################################
+# exceptions
+
+class MimimalError(Exception):
+ pass
import contextlib
-import itertools
import os
import pickle
-import sys
from textwrap import dedent
import threading
import unittest
_interpreters = import_helper.import_module('_interpreters')
-_testinternalcapi = import_helper.import_module('_testinternalcapi')
-from _interpreters import InterpreterNotFoundError, NotShareableError
+from _interpreters import InterpreterNotFoundError
##################################
_interpreters.is_shareable(obj))
-class ShareableTypeTests(unittest.TestCase):
-
- def _assert_values(self, values):
- for obj in values:
- with self.subTest(obj):
- 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 = _testinternalcapi.get_crossinterp_data(obj)
- got = _testinternalcapi.restore_crossinterp_data(xid)
-
- # XXX What about between interpreters?
- self.assertIs(got, obj)
-
- def test_types(self):
- self._assert_values([
- b'spam',
- 9999,
- ])
-
- def test_bytes(self):
- self._assert_values(i.to_bytes(2, 'little', signed=True)
- for i in range(-1, 258))
-
- def test_strs(self):
- self._assert_values(['hello world', '你好世界', ''])
-
- def test_int(self):
- self._assert_values(itertools.chain(range(-1, 258),
- [sys.maxsize, -sys.maxsize - 1]))
-
- def test_non_shareable_int(self):
- ints = [
- sys.maxsize + 1,
- -sys.maxsize - 2,
- 2**1000,
- ]
- for i in ints:
- with self.subTest(i):
- with self.assertRaises(NotShareableError) as cm:
- _testinternalcapi.get_crossinterp_data(i)
- self.assertIsInstance(cm.exception.__cause__, OverflowError)
-
- def test_bool(self):
- self._assert_values([True, False])
-
- def test_float(self):
- self._assert_values([0.0, 1.1, -1.0, 0.12345678, -0.12345678])
-
- def test_tuple(self):
- self._assert_values([(), (1,), ("hello", "world", ), (1, True, "hello")])
- # Test nesting
- self._assert_values([
- ((1,),),
- ((1, 2), (3, 4)),
- ((1, 2), (3, 4), (5, 6)),
- ])
-
- def test_tuples_containing_non_shareable_types(self):
- non_shareables = [
- Exception(),
- object(),
- ]
- for s in non_shareables:
- value = tuple([0, 1.0, s])
- with self.subTest(repr(value)):
- with self.assertRaises(NotShareableError):
- _testinternalcapi.get_crossinterp_data(value)
- # Check nested as well
- value = tuple([0, 1., (s,)])
- with self.subTest("nested " + repr(value)):
- with self.assertRaises(NotShareableError):
- _testinternalcapi.get_crossinterp_data(value)
-
-
class ModuleTests(TestBase):
def test_import_in_interpreter(self):
--- /dev/null
+import itertools
+import sys
+import types
+import unittest
+
+from test.support import import_helper
+
+_testinternalcapi = import_helper.import_module('_testinternalcapi')
+_interpreters = import_helper.import_module('_interpreters')
+from _interpreters import NotShareableError
+
+
+from test import _crossinterp_definitions as defs
+
+
+BUILTIN_TYPES = [o for _, o in __builtins__.items()
+ if isinstance(o, type)]
+EXCEPTION_TYPES = [cls for cls in BUILTIN_TYPES
+ if issubclass(cls, BaseException)]
+
+
+class _GetXIDataTests(unittest.TestCase):
+
+ MODE = None
+
+ def get_xidata(self, obj, *, mode=None):
+ mode = self._resolve_mode(mode)
+ return _testinternalcapi.get_crossinterp_data(obj, mode)
+
+ def get_roundtrip(self, obj, *, mode=None):
+ mode = self._resolve_mode(mode)
+ xid =_testinternalcapi.get_crossinterp_data(obj, mode)
+ return _testinternalcapi.restore_crossinterp_data(xid)
+
+ def iter_roundtrip_values(self, values, *, mode=None):
+ mode = self._resolve_mode(mode)
+ for obj in values:
+ with self.subTest(obj):
+ xid = _testinternalcapi.get_crossinterp_data(obj, mode)
+ got = _testinternalcapi.restore_crossinterp_data(xid)
+ yield obj, got
+
+ def assert_roundtrip_equal(self, values, *, mode=None):
+ for obj, got in self.iter_roundtrip_values(values, mode=mode):
+ self.assertEqual(got, obj)
+ self.assertIs(type(got), type(obj))
+
+ def assert_roundtrip_identical(self, values, *, mode=None):
+ for obj, got in self.iter_roundtrip_values(values, mode=mode):
+ # XXX What about between interpreters?
+ self.assertIs(got, obj)
+
+ def assert_not_shareable(self, values, exctype=None, *, mode=None):
+ mode = self._resolve_mode(mode)
+ for obj in values:
+ with self.subTest(obj):
+ with self.assertRaises(NotShareableError) as cm:
+ _testinternalcapi.get_crossinterp_data(obj, mode)
+ if exctype is not None:
+ self.assertIsInstance(cm.exception.__cause__, exctype)
+
+ def _resolve_mode(self, mode):
+ if mode is None:
+ mode = self.MODE
+ assert mode
+ return mode
+
+
+class ShareableTypeTests(_GetXIDataTests):
+
+ MODE = 'xidata'
+
+ def test_singletons(self):
+ self.assert_roundtrip_identical([
+ None,
+ True,
+ False,
+ ])
+ self.assert_not_shareable([
+ Ellipsis,
+ NotImplemented,
+ ])
+
+ def test_types(self):
+ self.assert_roundtrip_equal([
+ b'spam',
+ 9999,
+ ])
+
+ def test_bytes(self):
+ values = (i.to_bytes(2, 'little', signed=True)
+ for i in range(-1, 258))
+ self.assert_roundtrip_equal(values)
+
+ def test_strs(self):
+ self.assert_roundtrip_equal([
+ 'hello world',
+ '你好世界',
+ '',
+ ])
+
+ def test_int(self):
+ bounds = [sys.maxsize, -sys.maxsize - 1]
+ values = itertools.chain(range(-1, 258), bounds)
+ self.assert_roundtrip_equal(values)
+
+ def test_non_shareable_int(self):
+ ints = [
+ sys.maxsize + 1,
+ -sys.maxsize - 2,
+ 2**1000,
+ ]
+ self.assert_not_shareable(ints, OverflowError)
+
+ def test_float(self):
+ self.assert_roundtrip_equal([
+ 0.0,
+ 1.1,
+ -1.0,
+ 0.12345678,
+ -0.12345678,
+ ])
+
+ def test_tuple(self):
+ self.assert_roundtrip_equal([
+ (),
+ (1,),
+ ("hello", "world", ),
+ (1, True, "hello"),
+ ])
+ # Test nesting
+ self.assert_roundtrip_equal([
+ ((1,),),
+ ((1, 2), (3, 4)),
+ ((1, 2), (3, 4), (5, 6)),
+ ])
+
+ def test_tuples_containing_non_shareable_types(self):
+ non_shareables = [
+ Exception(),
+ object(),
+ ]
+ for s in non_shareables:
+ value = tuple([0, 1.0, s])
+ with self.subTest(repr(value)):
+ with self.assertRaises(NotShareableError):
+ self.get_xidata(value)
+ # Check nested as well
+ value = tuple([0, 1., (s,)])
+ with self.subTest("nested " + repr(value)):
+ with self.assertRaises(NotShareableError):
+ self.get_xidata(value)
+
+ # The rest are not shareable.
+
+ def test_object(self):
+ self.assert_not_shareable([
+ object(),
+ ])
+
+ def test_function_object(self):
+ for func in defs.FUNCTIONS:
+ assert type(func) is types.FunctionType, func
+ assert type(defs.SpamOkay.okay) is types.FunctionType, func
+ assert type(lambda: None) is types.LambdaType
+
+ self.assert_not_shareable([
+ *defs.FUNCTIONS,
+ defs.SpamOkay.okay,
+ (lambda: None),
+ ])
+
+ def test_builtin_function(self):
+ functions = [
+ len,
+ sys.is_finalizing,
+ sys.exit,
+ _testinternalcapi.get_crossinterp_data,
+ ]
+ for func in functions:
+ assert type(func) is types.BuiltinFunctionType, func
+
+ self.assert_not_shareable(functions)
+
+ def test_function_like(self):
+ self.assert_not_shareable(defs.FUNCTION_LIKE)
+
+ def test_builtin_wrapper(self):
+ _wrappers = {
+ defs.SpamOkay().okay: types.MethodType,
+ [].append: types.BuiltinMethodType,
+ dict.__dict__['fromkeys']: types.ClassMethodDescriptorType,
+ types.FunctionType.__code__: types.GetSetDescriptorType,
+ types.FunctionType.__globals__: types.MemberDescriptorType,
+ str.join: types.MethodDescriptorType,
+ object().__str__: types.MethodWrapperType,
+ object.__init__: types.WrapperDescriptorType,
+ }
+ for obj, expected in _wrappers.items():
+ assert type(obj) is expected, (obj, expected)
+
+ self.assert_not_shareable([
+ *_wrappers,
+ staticmethod(defs.SpamOkay.okay),
+ classmethod(defs.SpamOkay.okay),
+ property(defs.SpamOkay.okay),
+ ])
+
+ def test_module(self):
+ assert type(sys) is types.ModuleType, type(sys)
+ assert type(defs) is types.ModuleType, type(defs)
+ assert type(unittest) is types.ModuleType, type(defs)
+
+ assert 'emptymod' not in sys.modules
+ with import_helper.ready_to_import('emptymod', ''):
+ import emptymod
+
+ self.assert_not_shareable([
+ sys,
+ defs,
+ unittest,
+ emptymod,
+ ])
+
+ def test_class(self):
+ self.assert_not_shareable([
+ defs.Spam,
+ defs.SpamOkay,
+ defs.SpamFull,
+ defs.SubSpamFull,
+ defs.SubTuple,
+ defs.EggsNested,
+ ])
+ self.assert_not_shareable([
+ defs.Spam(),
+ defs.SpamOkay(),
+ defs.SpamFull(1, 2, 3),
+ defs.SubSpamFull(1, 2, 3),
+ defs.SubTuple([1, 2, 3]),
+ defs.EggsNested(),
+ ])
+
+ def test_builtin_type(self):
+ self.assert_not_shareable([
+ *BUILTIN_TYPES,
+ *(o for n, o in vars(types).items()
+ if (isinstance(o, type) and
+ n not in ('DynamicClassAttribute', '_GeneratorWrapper'))),
+ ])
+
+ def test_exception(self):
+ self.assert_not_shareable([
+ defs.MimimalError('error!'),
+ ])
+
+ def test_builtin_exception(self):
+ msg = 'error!'
+ try:
+ raise Exception
+ except Exception as exc:
+ caught = exc
+ special = {
+ BaseExceptionGroup: (msg, [caught]),
+ ExceptionGroup: (msg, [caught]),
+# UnicodeError: (None, msg, None, None, None),
+ UnicodeEncodeError: ('utf-8', '', 1, 3, msg),
+ UnicodeDecodeError: ('utf-8', b'', 1, 3, msg),
+ UnicodeTranslateError: ('', 1, 3, msg),
+ }
+ exceptions = []
+ for cls in EXCEPTION_TYPES:
+ args = special.get(cls) or (msg,)
+ exceptions.append(cls(*args))
+
+ self.assert_not_shareable(exceptions)
+
+ def test_builtin_objects(self):
+ ns = {}
+ exec("""if True:
+ try:
+ raise Exception
+ except Exception as exc:
+ TRACEBACK = exc.__traceback__
+ FRAME = TRACEBACK.tb_frame
+ """, ns, ns)
+
+ self.assert_not_shareable([
+ types.MappingProxyType({}),
+ types.SimpleNamespace(),
+ # types.CodeType
+ defs.spam_minimal.__code__,
+ defs.spam_full.__code__,
+ defs.spam_CC.__code__,
+ defs.eggs_closure_C.__code__,
+ defs.ham_C_closure.__code__,
+ # types.CellType
+ types.CellType(),
+ # types.FrameType
+ ns['FRAME'],
+ # types.TracebackType
+ ns['TRACEBACK'],
+ ])
+
+
+if __name__ == '__main__':
+ unittest.main()
}
// Convert the object to cross-interpreter data.
- _PyXIData_t *data = GLOBAL_MALLOC(_PyXIData_t);
+ _PyXIData_t *data = _PyXIData_New();
if (data == NULL) {
PyThread_release_lock(mutex);
return -1;
assert(queue != NULL);
// Convert the object to cross-interpreter data.
- _PyXIData_t *data = GLOBAL_MALLOC(_PyXIData_t);
+ _PyXIData_t *data = _PyXIData_New();
if (data == NULL) {
_queue_unmark_waiter(queue, queues->mutex);
return -1;
If a function is provided, its code object is used and all its state\n\
is ignored, including its __globals__ dict.");
-static PyObject *
-interp_call(PyObject *self, PyObject *args, PyObject *kwds)
-{
- static char *kwlist[] = {"id", "callable", "args", "kwargs",
- "restrict", NULL};
- PyObject *id, *callable;
- PyObject *args_obj = NULL;
- PyObject *kwargs_obj = NULL;
- int restricted = 0;
- if (!PyArg_ParseTupleAndKeywords(args, kwds,
- "OO|OO$p:" MODULE_NAME_STR ".call", kwlist,
- &id, &callable, &args_obj, &kwargs_obj,
- &restricted))
- {
- return NULL;
- }
-
- int reqready = 1;
- PyInterpreterState *interp = \
- resolve_interp(id, restricted, reqready, "make a call in");
- if (interp == NULL) {
- return NULL;
- }
-
- if (args_obj != NULL) {
- PyErr_SetString(PyExc_ValueError, "got unexpected args");
- return NULL;
- }
- if (kwargs_obj != NULL) {
- PyErr_SetString(PyExc_ValueError, "got unexpected kwargs");
- return NULL;
- }
-
- PyObject *code = (PyObject *)convert_code_arg(callable, MODULE_NAME_STR ".call",
- "argument 2", "a function");
- if (code == NULL) {
- return NULL;
- }
-
- PyObject *excinfo = NULL;
- int res = _interp_exec(self, interp, code, NULL, &excinfo);
- Py_DECREF(code);
- if (res < 0) {
- assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
- return excinfo;
- }
- Py_RETURN_NONE;
-}
-
-PyDoc_STRVAR(call_doc,
-"call(id, callable, args=None, kwargs=None, *, restrict=False)\n\
-\n\
-Call the provided object in the identified interpreter.\n\
-Pass the given args and kwargs, if possible.\n\
-\n\
-\"callable\" may be a plain function with no free vars that takes\n\
-no arguments.\n\
-\n\
-The function's code object is used and all its state\n\
-is ignored, including its __globals__ dict.");
-
static PyObject *
interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
{
\n\
(See " MODULE_NAME_STR ".exec().");
+static PyObject *
+interp_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"id", "callable", "args", "kwargs",
+ "restrict", NULL};
+ PyObject *id, *callable;
+ PyObject *args_obj = NULL;
+ PyObject *kwargs_obj = NULL;
+ int restricted = 0;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "OO|OO$p:" MODULE_NAME_STR ".call", kwlist,
+ &id, &callable, &args_obj, &kwargs_obj,
+ &restricted))
+ {
+ return NULL;
+ }
+
+ int reqready = 1;
+ PyInterpreterState *interp = \
+ resolve_interp(id, restricted, reqready, "make a call in");
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ if (args_obj != NULL) {
+ PyErr_SetString(PyExc_ValueError, "got unexpected args");
+ return NULL;
+ }
+ if (kwargs_obj != NULL) {
+ PyErr_SetString(PyExc_ValueError, "got unexpected kwargs");
+ return NULL;
+ }
+
+ PyObject *code = (PyObject *)convert_code_arg(callable, MODULE_NAME_STR ".call",
+ "argument 2", "a function");
+ if (code == NULL) {
+ return NULL;
+ }
+
+ PyObject *excinfo = NULL;
+ int res = _interp_exec(self, interp, code, NULL, &excinfo);
+ Py_DECREF(code);
+ if (res < 0) {
+ assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
+ return excinfo;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(call_doc,
+"call(id, callable, args=None, kwargs=None, *, restrict=False)\n\
+\n\
+Call the provided object in the identified interpreter.\n\
+Pass the given args and kwargs, if possible.\n\
+\n\
+\"callable\" may be a plain function with no free vars that takes\n\
+no arguments.\n\
+\n\
+The function's code object is used and all its state\n\
+is ignored, including its __globals__ dict.");
+
static PyObject *
object_is_shareable(PyObject *self, PyObject *args, PyObject *kwds)
static void
_xid_capsule_destructor(PyObject *capsule)
{
- _PyXIData_t *data = (_PyXIData_t *)PyCapsule_GetPointer(capsule, NULL);
- if (data != NULL) {
- assert(_PyXIData_Release(data) == 0);
- _PyXIData_Free(data);
+ _PyXIData_t *xidata = (_PyXIData_t *)PyCapsule_GetPointer(capsule, NULL);
+ if (xidata != NULL) {
+ assert(_PyXIData_Release(xidata) == 0);
+ _PyXIData_Free(xidata);
}
}
static PyObject *
-get_crossinterp_data(PyObject *self, PyObject *args)
+get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs)
{
- PyThreadState *tstate = _PyThreadState_GET();
-
PyObject *obj = NULL;
- if (!PyArg_ParseTuple(args, "O:get_crossinterp_data", &obj)) {
+ PyObject *modeobj = NULL;
+ static char *kwlist[] = {"obj", "mode", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "O|O:get_crossinterp_data", kwlist,
+ &obj, &modeobj))
+ {
return NULL;
}
-
- _PyXIData_t *data = _PyXIData_New();
- if (data == NULL) {
+ const char *mode = NULL;
+ if (modeobj == NULL || modeobj == Py_None) {
+ mode = "xidata";
+ }
+ else if (!PyUnicode_Check(modeobj)) {
+ PyErr_Format(PyExc_TypeError, "expected mode str, got %R", modeobj);
return NULL;
}
- if (_PyObject_GetXIData(tstate, obj, data) != 0) {
- _PyXIData_Free(data);
+ else {
+ mode = PyUnicode_AsUTF8(modeobj);
+ if (strlen(mode) == 0) {
+ mode = "xidata";
+ }
+ }
+
+ PyThreadState *tstate = _PyThreadState_GET();
+ _PyXIData_t *xidata = _PyXIData_New();
+ if (xidata == NULL) {
return NULL;
}
- PyObject *capsule = PyCapsule_New(data, NULL, _xid_capsule_destructor);
+ if (strcmp(mode, "xidata") == 0) {
+ if (_PyObject_GetXIData(tstate, obj, xidata) != 0) {
+ goto error;
+ }
+ }
+ else {
+ PyErr_Format(PyExc_ValueError, "unsupported mode %R", modeobj);
+ goto error;
+ }
+ PyObject *capsule = PyCapsule_New(xidata, NULL, _xid_capsule_destructor);
if (capsule == NULL) {
- assert(_PyXIData_Release(data) == 0);
- _PyXIData_Free(data);
+ assert(_PyXIData_Release(xidata) == 0);
+ goto error;
}
return capsule;
+
+error:
+ _PyXIData_Free(xidata);
+ return NULL;
}
static PyObject *
return NULL;
}
- _PyXIData_t *data = (_PyXIData_t *)PyCapsule_GetPointer(capsule, NULL);
- if (data == NULL) {
+ _PyXIData_t *xidata = (_PyXIData_t *)PyCapsule_GetPointer(capsule, NULL);
+ if (xidata == NULL) {
return NULL;
}
- return _PyXIData_NewObject(data);
+ return _PyXIData_NewObject(xidata);
}
{"interpreter_refcount_linked", interpreter_refcount_linked, METH_O},
{"compile_perf_trampoline_entry", compile_perf_trampoline_entry, METH_VARARGS},
{"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS},
- {"get_crossinterp_data", get_crossinterp_data, METH_VARARGS},
+ {"get_crossinterp_data", _PyCFunction_CAST(get_crossinterp_data),
+ METH_VARARGS | METH_KEYWORDS},
{"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
_TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF
{"get_rare_event_counters", get_rare_event_counters, METH_NOARGS},
_PyXIData_t *
_PyXIData_New(void)
{
- _PyXIData_t *xid = PyMem_RawMalloc(sizeof(_PyXIData_t));
+ _PyXIData_t *xid = PyMem_RawCalloc(1, sizeof(_PyXIData_t));
if (xid == NULL) {
PyErr_NoMemory();
}
/* defining cross-interpreter data */
static inline void
-_xidata_init(_PyXIData_t *data)
+_xidata_init(_PyXIData_t *xidata)
{
// If the value is being reused
// then _xidata_clear() should have been called already.
- assert(data->data == NULL);
- assert(data->obj == NULL);
- *data = (_PyXIData_t){0};
- _PyXIData_INTERPID(data) = -1;
+ assert(xidata->data == NULL);
+ assert(xidata->obj == NULL);
+ *xidata = (_PyXIData_t){0};
+ _PyXIData_INTERPID(xidata) = -1;
}
static inline void
-_xidata_clear(_PyXIData_t *data)
+_xidata_clear(_PyXIData_t *xidata)
{
// _PyXIData_t only has two members that need to be
- // cleaned up, if set: "data" must be freed and "obj" must be decref'ed.
+ // cleaned up, if set: "xidata" 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);
+ if (xidata->data != NULL) {
+ if (xidata->free != NULL) {
+ xidata->free(xidata->data);
}
- data->data = NULL;
+ xidata->data = NULL;
}
- Py_CLEAR(data->obj);
+ Py_CLEAR(xidata->obj);
}
void
-_PyXIData_Init(_PyXIData_t *data,
+_PyXIData_Init(_PyXIData_t *xidata,
PyInterpreterState *interp,
void *shared, PyObject *obj,
xid_newobjfunc new_object)
{
- assert(data != NULL);
+ assert(xidata != NULL);
assert(new_object != NULL);
- _xidata_init(data);
- data->data = shared;
+ _xidata_init(xidata);
+ xidata->data = shared;
if (obj != NULL) {
assert(interp != NULL);
// released in _PyXIData_Clear()
- data->obj = Py_NewRef(obj);
+ xidata->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).
- _PyXIData_INTERPID(data) = (interp != NULL)
+ _PyXIData_INTERPID(xidata) = (interp != NULL)
? PyInterpreterState_GetID(interp)
: -1;
- data->new_object = new_object;
+ xidata->new_object = new_object;
}
int
-_PyXIData_InitWithSize(_PyXIData_t *data,
+_PyXIData_InitWithSize(_PyXIData_t *xidata,
PyInterpreterState *interp,
const size_t size, PyObject *obj,
xid_newobjfunc new_object)
// For now we always free the shared data in the same interpreter
// where it was allocated, so the interpreter is required.
assert(interp != NULL);
- _PyXIData_Init(data, interp, NULL, obj, new_object);
- data->data = PyMem_RawMalloc(size);
- if (data->data == NULL) {
+ _PyXIData_Init(xidata, interp, NULL, obj, new_object);
+ xidata->data = PyMem_RawMalloc(size);
+ if (xidata->data == NULL) {
return -1;
}
- data->free = PyMem_RawFree;
+ xidata->free = PyMem_RawFree;
return 0;
}
void
-_PyXIData_Clear(PyInterpreterState *interp, _PyXIData_t *data)
+_PyXIData_Clear(PyInterpreterState *interp, _PyXIData_t *xidata)
{
- assert(data != NULL);
+ assert(xidata != NULL);
// This must be called in the owning interpreter.
assert(interp == NULL
- || _PyXIData_INTERPID(data) == -1
- || _PyXIData_INTERPID(data) == PyInterpreterState_GetID(interp));
- _xidata_clear(data);
+ || _PyXIData_INTERPID(xidata) == -1
+ || _PyXIData_INTERPID(xidata) == PyInterpreterState_GetID(interp));
+ _xidata_clear(xidata);
}
-/* using cross-interpreter data */
-
-static int
-_check_xidata(PyThreadState *tstate, _PyXIData_t *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 (_PyXIData_INTERPID(data) < 0) {
- PyErr_SetString(PyExc_SystemError, "missing interp");
- return -1;
- }
-
- if (data->new_object == NULL) {
- PyErr_SetString(PyExc_SystemError, "missing new_object func");
- return -1;
- }
-
- // data->free may be NULL, so we don't check it.
-
- return 0;
-}
+/* getting cross-interpreter data */
static inline void
_set_xid_lookup_failure(PyThreadState *tstate, PyObject *obj, const char *msg,
}
}
+
int
_PyObject_CheckXIData(PyThreadState *tstate, PyObject *obj)
{
return 0;
}
+static int
+_check_xidata(PyThreadState *tstate, _PyXIData_t *xidata)
+{
+ // xidata->data can be anything, including NULL, so we don't check it.
+
+ // xidata->obj may be NULL, so we don't check it.
+
+ if (_PyXIData_INTERPID(xidata) < 0) {
+ PyErr_SetString(PyExc_SystemError, "missing interp");
+ return -1;
+ }
+
+ if (xidata->new_object == NULL) {
+ PyErr_SetString(PyExc_SystemError, "missing new_object func");
+ return -1;
+ }
+
+ // xidata->free may be NULL, so we don't check it.
+
+ return 0;
+}
+
int
_PyObject_GetXIData(PyThreadState *tstate,
- PyObject *obj, _PyXIData_t *data)
+ PyObject *obj, _PyXIData_t *xidata)
{
PyInterpreterState *interp = tstate->interp;
- // Reset data before re-populating.
- *data = (_PyXIData_t){0};
- _PyXIData_INTERPID(data) = -1;
+ assert(xidata->data == NULL);
+ assert(xidata->obj == NULL);
+ if (xidata->data != NULL || xidata->obj != NULL) {
+ _PyErr_SetString(tstate, PyExc_ValueError, "xidata not cleared");
+ }
// Call the "getdata" func for the object.
dlcontext_t ctx;
Py_INCREF(obj);
xidatafunc getdata = lookup_getdata(&ctx, obj);
if (getdata == NULL) {
+ if (PyErr_Occurred()) {
+ Py_DECREF(obj);
+ return -1;
+ }
+ // Fall back to obj
Py_DECREF(obj);
if (!_PyErr_Occurred(tstate)) {
_set_xid_lookup_failure(tstate, obj, NULL, NULL);
}
return -1;
}
- int res = getdata(tstate, obj, data);
+ int res = getdata(tstate, obj, xidata);
Py_DECREF(obj);
if (res != 0) {
PyObject *cause = _PyErr_GetRaisedException(tstate);
}
// Fill in the blanks and validate the result.
- _PyXIData_INTERPID(data) = PyInterpreterState_GetID(interp);
- if (_check_xidata(tstate, data) != 0) {
- (void)_PyXIData_Release(data);
+ _PyXIData_INTERPID(xidata) = PyInterpreterState_GetID(interp);
+ if (_check_xidata(tstate, xidata) != 0) {
+ (void)_PyXIData_Release(xidata);
return -1;
}
return 0;
}
+
+/* using cross-interpreter data */
+
PyObject *
-_PyXIData_NewObject(_PyXIData_t *data)
+_PyXIData_NewObject(_PyXIData_t *xidata)
{
- return data->new_object(data);
+ return xidata->new_object(xidata);
}
static int
}
static int
-_xidata_release(_PyXIData_t *data, int rawfree)
+_xidata_release(_PyXIData_t *xidata, int rawfree)
{
- if ((data->data == NULL || data->free == NULL) && data->obj == NULL) {
+ if ((xidata->data == NULL || xidata->free == NULL) && xidata->obj == NULL) {
// Nothing to release!
if (rawfree) {
- PyMem_RawFree(data);
+ PyMem_RawFree(xidata);
}
else {
- data->data = NULL;
+ xidata->data = NULL;
}
return 0;
}
// Switch to the original interpreter.
PyInterpreterState *interp = _PyInterpreterState_LookUpID(
- _PyXIData_INTERPID(data));
+ _PyXIData_INTERPID(xidata));
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);
+ PyMem_RawFree(xidata);
}
return -1;
}
// "Release" the data and/or the object.
if (rawfree) {
- return _Py_CallInInterpreterAndRawFree(interp, _call_clear_xidata, data);
+ return _Py_CallInInterpreterAndRawFree(interp, _call_clear_xidata, xidata);
}
else {
- return _Py_CallInInterpreter(interp, _call_clear_xidata, data);
+ return _Py_CallInInterpreter(interp, _call_clear_xidata, xidata);
}
}
int
-_PyXIData_Release(_PyXIData_t *data)
+_PyXIData_Release(_PyXIData_t *xidata)
{
- return _xidata_release(data, 0);
+ return _xidata_release(xidata, 0);
}
int
-_PyXIData_ReleaseAndRawFree(_PyXIData_t *data)
+_PyXIData_ReleaseAndRawFree(_PyXIData_t *xidata)
{
- return _xidata_release(data, 1);
+ return _xidata_release(xidata, 1);
}
static int
-_release_xid_data(_PyXIData_t *data, int rawfree)
+_release_xid_data(_PyXIData_t *xidata, int rawfree)
{
PyObject *exc = PyErr_GetRaisedException();
int res = rawfree
- ? _PyXIData_Release(data)
- : _PyXIData_ReleaseAndRawFree(data);
+ ? _PyXIData_Release(xidata)
+ : _PyXIData_ReleaseAndRawFree(xidata);
if (res < 0) {
/* The owning interpreter is already destroyed. */
- _PyXIData_Clear(NULL, data);
+ _PyXIData_Clear(NULL, xidata);
// XXX Emit a warning?
PyErr_Clear();
}
typedef struct _sharednsitem {
const char *name;
- _PyXIData_t *data;
+ _PyXIData_t *xidata;
// We could have a "PyXIData _data" field, so it would
// be allocated as part of the item and avoid an extra allocation.
// However, doing so adds a bunch of complexity because we must
assert(!_sharednsitem_is_initialized(item));
return -1;
}
- item->data = NULL;
+ item->xidata = NULL;
assert(_sharednsitem_is_initialized(item));
return 0;
}
static int
_sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid)
{
- if (item->data == NULL) {
+ if (item->xidata == NULL) {
return 0;
}
if (p_interpid != NULL) {
- *p_interpid = _PyXIData_INTERPID(item->data);
+ *p_interpid = _PyXIData_INTERPID(item->xidata);
}
return 1;
}
_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
{
assert(_sharednsitem_is_initialized(item));
- assert(item->data == NULL);
- item->data = PyMem_RawMalloc(sizeof(_PyXIData_t));
- if (item->data == NULL) {
- PyErr_NoMemory();
+ assert(item->xidata == NULL);
+ item->xidata = _PyXIData_New();
+ if (item->xidata == NULL) {
return -1;
}
PyThreadState *tstate = PyThreadState_Get();
- if (_PyObject_GetXIData(tstate, value, item->data) != 0) {
- PyMem_RawFree(item->data);
- item->data = NULL;
+ if (_PyObject_GetXIData(tstate, value, item->xidata) != 0) {
+ PyMem_RawFree(item->xidata);
+ item->xidata = NULL;
// The caller may want to propagate PyExc_NotShareableError
// if currently switched between interpreters.
return -1;
static void
_sharednsitem_clear_value(_PyXI_namespace_item *item)
{
- _PyXIData_t *data = item->data;
- if (data != NULL) {
- item->data = NULL;
+ _PyXIData_t *xidata = item->xidata;
+ if (xidata != NULL) {
+ item->xidata = NULL;
int rawfree = 1;
- (void)_release_xid_data(data, rawfree);
+ (void)_release_xid_data(xidata, rawfree);
}
}
_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns)
{
assert(item->name != NULL);
- assert(item->data == NULL);
+ assert(item->xidata == NULL);
PyObject *value = PyDict_GetItemString(ns, item->name); // borrowed
if (value == NULL) {
if (PyErr_Occurred()) {
return -1;
}
PyObject *value;
- if (item->data != NULL) {
- value = _PyXIData_NewObject(item->data);
+ if (item->xidata != NULL) {
+ value = _PyXIData_NewObject(item->xidata);
if (value == NULL) {
Py_DECREF(name);
return -1;
};
static PyObject *
-_new_bytes_object(_PyXIData_t *data)
+_new_bytes_object(_PyXIData_t *xidata)
{
- struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(data->data);
+ struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(xidata->data);
return PyBytes_FromStringAndSize(shared->bytes, shared->len);
}
static int
-_bytes_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
+_bytes_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{
if (_PyXIData_InitWithSize(
- data, tstate->interp, sizeof(struct _shared_bytes_data), obj,
+ xidata, 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;
+ struct _shared_bytes_data *shared = (struct _shared_bytes_data *)xidata->data;
if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) {
- _PyXIData_Clear(tstate->interp, data);
+ _PyXIData_Clear(tstate->interp, xidata);
return -1;
}
return 0;
};
static PyObject *
-_new_str_object(_PyXIData_t *data)
+_new_str_object(_PyXIData_t *xidata)
{
- struct _shared_str_data *shared = (struct _shared_str_data *)(data->data);
+ struct _shared_str_data *shared = (struct _shared_str_data *)(xidata->data);
return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len);
}
static int
-_str_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
+_str_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{
if (_PyXIData_InitWithSize(
- data, tstate->interp, sizeof(struct _shared_str_data), obj,
+ xidata, 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;
+ struct _shared_str_data *shared = (struct _shared_str_data *)xidata->data;
shared->kind = PyUnicode_KIND(obj);
shared->buffer = PyUnicode_DATA(obj);
shared->len = PyUnicode_GET_LENGTH(obj);
// int
static PyObject *
-_new_long_object(_PyXIData_t *data)
+_new_long_object(_PyXIData_t *xidata)
{
- return PyLong_FromSsize_t((Py_ssize_t)(data->data));
+ return PyLong_FromSsize_t((Py_ssize_t)(xidata->data));
}
static int
-_long_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
+_long_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{
/* Note that this means the size of shareable ints is bounded by
* sys.maxsize. Hence on 32-bit architectures that is half the
}
return -1;
}
- _PyXIData_Init(data, tstate->interp, (void *)value, NULL, _new_long_object);
- // data->obj and data->free remain NULL
+ _PyXIData_Init(xidata, tstate->interp, (void *)value, NULL, _new_long_object);
+ // xidata->obj and xidata->free remain NULL
return 0;
}
// float
static PyObject *
-_new_float_object(_PyXIData_t *data)
+_new_float_object(_PyXIData_t *xidata)
{
- double * value_ptr = data->data;
+ double * value_ptr = xidata->data;
return PyFloat_FromDouble(*value_ptr);
}
static int
-_float_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
+_float_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{
if (_PyXIData_InitWithSize(
- data, tstate->interp, sizeof(double), NULL,
+ xidata, tstate->interp, sizeof(double), NULL,
_new_float_object
) < 0)
{
return -1;
}
- double *shared = (double *)data->data;
+ double *shared = (double *)xidata->data;
*shared = PyFloat_AsDouble(obj);
return 0;
}
// None
static PyObject *
-_new_none_object(_PyXIData_t *data)
+_new_none_object(_PyXIData_t *xidata)
{
// XXX Singleton refcounts are problematic across interpreters...
return Py_NewRef(Py_None);
}
static int
-_none_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
+_none_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{
- _PyXIData_Init(data, tstate->interp, NULL, NULL, _new_none_object);
- // data->data, data->obj and data->free remain NULL
+ _PyXIData_Init(xidata, tstate->interp, NULL, NULL, _new_none_object);
+ // xidata->data, xidata->obj and xidata->free remain NULL
return 0;
}
// bool
static PyObject *
-_new_bool_object(_PyXIData_t *data)
+_new_bool_object(_PyXIData_t *xidata)
{
- if (data->data){
+ if (xidata->data){
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
static int
-_bool_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
+_bool_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{
- _PyXIData_Init(data, tstate->interp,
+ _PyXIData_Init(xidata, tstate->interp,
(void *) (Py_IsTrue(obj) ? (uintptr_t) 1 : (uintptr_t) 0), NULL,
_new_bool_object);
- // data->obj and data->free remain NULL
+ // xidata->obj and xidata->free remain NULL
return 0;
}
struct _shared_tuple_data {
Py_ssize_t len;
- _PyXIData_t **data;
+ _PyXIData_t **items;
};
static PyObject *
-_new_tuple_object(_PyXIData_t *data)
+_new_tuple_object(_PyXIData_t *xidata)
{
- struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data->data);
+ struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(xidata->data);
PyObject *tuple = PyTuple_New(shared->len);
if (tuple == NULL) {
return NULL;
}
for (Py_ssize_t i = 0; i < shared->len; i++) {
- PyObject *item = _PyXIData_NewObject(shared->data[i]);
+ PyObject *item = _PyXIData_NewObject(shared->items[i]);
if (item == NULL){
Py_DECREF(tuple);
return NULL;
int64_t interpid = PyInterpreterState_GetID(_PyInterpreterState_GET());
#endif
for (Py_ssize_t i = 0; i < shared->len; i++) {
- if (shared->data[i] != NULL) {
- assert(_PyXIData_INTERPID(shared->data[i]) == interpid);
- _PyXIData_Release(shared->data[i]);
- PyMem_RawFree(shared->data[i]);
- shared->data[i] = NULL;
+ if (shared->items[i] != NULL) {
+ assert(_PyXIData_INTERPID(shared->items[i]) == interpid);
+ _PyXIData_Release(shared->items[i]);
+ PyMem_RawFree(shared->items[i]);
+ shared->items[i] = NULL;
}
}
- PyMem_Free(shared->data);
+ PyMem_Free(shared->items);
PyMem_RawFree(shared);
}
static int
-_tuple_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
+_tuple_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{
Py_ssize_t len = PyTuple_GET_SIZE(obj);
if (len < 0) {
}
shared->len = len;
- shared->data = (_PyXIData_t **) PyMem_Calloc(shared->len, sizeof(_PyXIData_t *));
- if (shared->data == NULL) {
+ shared->items = (_PyXIData_t **) PyMem_Calloc(shared->len, sizeof(_PyXIData_t *));
+ if (shared->items == NULL) {
PyErr_NoMemory();
return -1;
}
for (Py_ssize_t i = 0; i < shared->len; i++) {
- _PyXIData_t *data = _PyXIData_New();
- if (data == NULL) {
+ _PyXIData_t *xidata_i = _PyXIData_New();
+ if (xidata_i == NULL) {
goto error; // PyErr_NoMemory already set
}
PyObject *item = PyTuple_GET_ITEM(obj, i);
int res = -1;
if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) {
- res = _PyObject_GetXIData(tstate, item, data);
+ res = _PyObject_GetXIData(tstate, item, xidata_i);
_Py_LeaveRecursiveCallTstate(tstate);
}
if (res < 0) {
- PyMem_RawFree(data);
+ PyMem_RawFree(xidata_i);
goto error;
}
- shared->data[i] = data;
+ shared->items[i] = xidata_i;
}
- _PyXIData_Init(data, tstate->interp, shared, obj, _new_tuple_object);
- data->free = _tuple_shared_free;
+ _PyXIData_Init(xidata, tstate->interp, shared, obj, _new_tuple_object);
+ _PyXIData_SET_FREE(xidata, _tuple_shared_free);
return 0;
error: