gh-132775: Cleanup Related to crossinterp.c Before Further Changes (gh-132974)

This change consists of adding tests and moving code around, with some renaming thrown in.
This commit is contained in:
Eric Snow 2025-04-28 11:55:15 -06:00 committed by GitHub
parent b739ec5ab7
commit 6f04325992
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 838 additions and 348 deletions

View file

@ -57,7 +57,7 @@ struct _xidata {
// likely a registered "xidatafunc", is responsible for // likely a registered "xidatafunc", is responsible for
// ensuring it owns the reference (i.e. incref). // ensuring it owns the reference (i.e. incref).
PyObject *obj; 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 // object. It corresponds to the active interpreter when
// _PyObject_GetXIData() was called. This should only // _PyObject_GetXIData() was called. This should only
// be set by the cross-interpreter machinery. // be set by the cross-interpreter machinery.
@ -93,6 +93,38 @@ PyAPI_FUNC(void) _PyXIData_Free(_PyXIData_t *data);
// Users should not need getters for "new_object" or "free". // Users should not need getters for "new_object" or "free".
/* defining cross-interpreter data */
PyAPI_FUNC(void) _PyXIData_Init(
_PyXIData_t *data,
PyInterpreterState *interp, void *shared, PyObject *obj,
xid_newobjfunc new_object);
PyAPI_FUNC(int) _PyXIData_InitWithSize(
_PyXIData_t *,
PyInterpreterState *interp, const size_t, PyObject *,
xid_newobjfunc);
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,
// though that should be infrequent.
#define _PyXIData_SET_FREE(DATA, FUNC) \
do { \
(DATA)->free = (FUNC); \
} while (0)
// Additionally, some shareable types are essentially light wrappers
// around other shareable types. The xidatafunc of the wrapper
// can often be implemented by calling the wrapped object's
// xidatafunc and then changing the "new_object" function.
// We have _PyXIData_SET_NEW_OBJECT() here for that,
// but might be better to have a function like
// _PyXIData_AdaptToWrapper() instead.
#define _PyXIData_SET_NEW_OBJECT(DATA, FUNC) \
do { \
(DATA)->new_object = (FUNC); \
} while (0)
/* getting cross-interpreter data */ /* getting cross-interpreter data */
typedef int (*xidatafunc)(PyThreadState *tstate, PyObject *, _PyXIData_t *); typedef int (*xidatafunc)(PyThreadState *tstate, PyObject *, _PyXIData_t *);
@ -124,38 +156,6 @@ PyAPI_FUNC(int) _PyXIData_Release(_PyXIData_t *);
PyAPI_FUNC(int) _PyXIData_ReleaseAndRawFree(_PyXIData_t *); PyAPI_FUNC(int) _PyXIData_ReleaseAndRawFree(_PyXIData_t *);
/* defining cross-interpreter data */
PyAPI_FUNC(void) _PyXIData_Init(
_PyXIData_t *data,
PyInterpreterState *interp, void *shared, PyObject *obj,
xid_newobjfunc new_object);
PyAPI_FUNC(int) _PyXIData_InitWithSize(
_PyXIData_t *,
PyInterpreterState *interp, const size_t, PyObject *,
xid_newobjfunc);
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,
// though that should be infrequent.
#define _PyXIData_SET_FREE(DATA, FUNC) \
do { \
(DATA)->free = (FUNC); \
} while (0)
// Additionally, some shareable types are essentially light wrappers
// around other shareable types. The xidatafunc of the wrapper
// can often be implemented by calling the wrapped object's
// xidatafunc and then changing the "new_object" function.
// We have _PyXIData_SET_NEW_OBJECT() here for that,
// but might be better to have a function like
// _PyXIData_AdaptToWrapper() instead.
#define _PyXIData_SET_NEW_OBJECT(DATA, FUNC) \
do { \
(DATA)->new_object = (FUNC); \
} while (0)
/* cross-interpreter data registry */ /* cross-interpreter data registry */
#define Py_CORE_CROSSINTERP_DATA_REGISTRY_H #define Py_CORE_CROSSINTERP_DATA_REGISTRY_H

View file

@ -0,0 +1,230 @@
# 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

View file

@ -1,8 +1,6 @@
import contextlib import contextlib
import itertools
import os import os
import pickle import pickle
import sys
from textwrap import dedent from textwrap import dedent
import threading import threading
import unittest import unittest
@ -14,8 +12,7 @@ from test.support import script_helper
_interpreters = import_helper.import_module('_interpreters') _interpreters = import_helper.import_module('_interpreters')
_testinternalcapi = import_helper.import_module('_testinternalcapi') from _interpreters import InterpreterNotFoundError
from _interpreters import InterpreterNotFoundError, NotShareableError
################################## ##################################
@ -144,87 +141,6 @@ class IsShareableTests(unittest.TestCase):
_interpreters.is_shareable(obj)) _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): class ModuleTests(TestBase):
def test_import_in_interpreter(self): def test_import_in_interpreter(self):

View file

@ -0,0 +1,306 @@
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()

View file

@ -1774,7 +1774,7 @@ channel_send(_channels *channels, int64_t cid, PyObject *obj,
} }
// Convert the object to cross-interpreter data. // Convert the object to cross-interpreter data.
_PyXIData_t *data = GLOBAL_MALLOC(_PyXIData_t); _PyXIData_t *data = _PyXIData_New();
if (data == NULL) { if (data == NULL) {
PyThread_release_lock(mutex); PyThread_release_lock(mutex);
return -1; return -1;

View file

@ -1138,7 +1138,7 @@ queue_put(_queues *queues, int64_t qid, PyObject *obj, int fmt, int unboundop)
assert(queue != NULL); assert(queue != NULL);
// Convert the object to cross-interpreter data. // Convert the object to cross-interpreter data.
_PyXIData_t *data = GLOBAL_MALLOC(_PyXIData_t); _PyXIData_t *data = _PyXIData_New();
if (data == NULL) { if (data == NULL) {
_queue_unmark_waiter(queue, queues->mutex); _queue_unmark_waiter(queue, queues->mutex);
return -1; return -1;

View file

@ -1115,67 +1115,6 @@ The code/function must not take any arguments or be a closure\n\
If a function is provided, its code object is used and all its state\n\ If a function is provided, its code object is used and all its state\n\
is ignored, including its __globals__ dict."); 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 * static PyObject *
interp_run_string(PyObject *self, PyObject *args, PyObject *kwds) interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
{ {
@ -1267,6 +1206,67 @@ are not supported. Methods and other callables are not supported either.\n\
\n\ \n\
(See " MODULE_NAME_STR ".exec()."); (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 * static PyObject *
object_is_shareable(PyObject *self, PyObject *args, PyObject *kwds) object_is_shareable(PyObject *self, PyObject *args, PyObject *kwds)

View file

@ -1686,37 +1686,64 @@ interpreter_refcount_linked(PyObject *self, PyObject *idobj)
static void static void
_xid_capsule_destructor(PyObject *capsule) _xid_capsule_destructor(PyObject *capsule)
{ {
_PyXIData_t *data = (_PyXIData_t *)PyCapsule_GetPointer(capsule, NULL); _PyXIData_t *xidata = (_PyXIData_t *)PyCapsule_GetPointer(capsule, NULL);
if (data != NULL) { if (xidata != NULL) {
assert(_PyXIData_Release(data) == 0); assert(_PyXIData_Release(xidata) == 0);
_PyXIData_Free(data); _PyXIData_Free(xidata);
} }
} }
static PyObject * 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; 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; return 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;
}
else {
mode = PyUnicode_AsUTF8(modeobj);
if (strlen(mode) == 0) {
mode = "xidata";
}
}
_PyXIData_t *data = _PyXIData_New(); PyThreadState *tstate = _PyThreadState_GET();
if (data == NULL) { _PyXIData_t *xidata = _PyXIData_New();
if (xidata == NULL) {
return NULL; return NULL;
} }
if (_PyObject_GetXIData(tstate, obj, data) != 0) { if (strcmp(mode, "xidata") == 0) {
_PyXIData_Free(data); if (_PyObject_GetXIData(tstate, obj, xidata) != 0) {
return NULL; goto error;
} }
PyObject *capsule = PyCapsule_New(data, NULL, _xid_capsule_destructor); }
else {
PyErr_Format(PyExc_ValueError, "unsupported mode %R", modeobj);
goto error;
}
PyObject *capsule = PyCapsule_New(xidata, NULL, _xid_capsule_destructor);
if (capsule == NULL) { if (capsule == NULL) {
assert(_PyXIData_Release(data) == 0); assert(_PyXIData_Release(xidata) == 0);
_PyXIData_Free(data); goto error;
} }
return capsule; return capsule;
error:
_PyXIData_Free(xidata);
return NULL;
} }
static PyObject * static PyObject *
@ -1727,11 +1754,11 @@ restore_crossinterp_data(PyObject *self, PyObject *args)
return NULL; return NULL;
} }
_PyXIData_t *data = (_PyXIData_t *)PyCapsule_GetPointer(capsule, NULL); _PyXIData_t *xidata = (_PyXIData_t *)PyCapsule_GetPointer(capsule, NULL);
if (data == NULL) { if (xidata == NULL) {
return NULL; return NULL;
} }
return _PyXIData_NewObject(data); return _PyXIData_NewObject(xidata);
} }
@ -2071,7 +2098,8 @@ static PyMethodDef module_functions[] = {
{"interpreter_refcount_linked", interpreter_refcount_linked, METH_O}, {"interpreter_refcount_linked", interpreter_refcount_linked, METH_O},
{"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}, {"get_crossinterp_data", _PyCFunction_CAST(get_crossinterp_data),
METH_VARARGS | METH_KEYWORDS},
{"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS}, {"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
_TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF _TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF
{"get_rare_event_counters", get_rare_event_counters, METH_NOARGS}, {"get_rare_event_counters", get_rare_event_counters, METH_NOARGS},

View file

@ -74,7 +74,7 @@ static xidatafunc lookup_getdata(struct _dlcontext *, PyObject *);
_PyXIData_t * _PyXIData_t *
_PyXIData_New(void) _PyXIData_New(void)
{ {
_PyXIData_t *xid = PyMem_RawMalloc(sizeof(_PyXIData_t)); _PyXIData_t *xid = PyMem_RawCalloc(1, sizeof(_PyXIData_t));
if (xid == NULL) { if (xid == NULL) {
PyErr_NoMemory(); PyErr_NoMemory();
} }
@ -93,58 +93,58 @@ _PyXIData_Free(_PyXIData_t *xid)
/* defining cross-interpreter data */ /* defining cross-interpreter data */
static inline void static inline void
_xidata_init(_PyXIData_t *data) _xidata_init(_PyXIData_t *xidata)
{ {
// If the value is being reused // If the value is being reused
// then _xidata_clear() should have been called already. // then _xidata_clear() should have been called already.
assert(data->data == NULL); assert(xidata->data == NULL);
assert(data->obj == NULL); assert(xidata->obj == NULL);
*data = (_PyXIData_t){0}; *xidata = (_PyXIData_t){0};
_PyXIData_INTERPID(data) = -1; _PyXIData_INTERPID(xidata) = -1;
} }
static inline void static inline void
_xidata_clear(_PyXIData_t *data) _xidata_clear(_PyXIData_t *xidata)
{ {
// _PyXIData_t only has two members that need to be // _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, // In both cases the original (owning) interpreter must be used,
// which is the caller's responsibility to ensure. // which is the caller's responsibility to ensure.
if (data->data != NULL) { if (xidata->data != NULL) {
if (data->free != NULL) { if (xidata->free != NULL) {
data->free(data->data); xidata->free(xidata->data);
} }
data->data = NULL; xidata->data = NULL;
} }
Py_CLEAR(data->obj); Py_CLEAR(xidata->obj);
} }
void void
_PyXIData_Init(_PyXIData_t *data, _PyXIData_Init(_PyXIData_t *xidata,
PyInterpreterState *interp, PyInterpreterState *interp,
void *shared, PyObject *obj, void *shared, PyObject *obj,
xid_newobjfunc new_object) xid_newobjfunc new_object)
{ {
assert(data != NULL); assert(xidata != NULL);
assert(new_object != NULL); assert(new_object != NULL);
_xidata_init(data); _xidata_init(xidata);
data->data = shared; xidata->data = shared;
if (obj != NULL) { if (obj != NULL) {
assert(interp != NULL); assert(interp != NULL);
// released in _PyXIData_Clear() // released in _PyXIData_Clear()
data->obj = Py_NewRef(obj); xidata->obj = Py_NewRef(obj);
} }
// Ideally every object would know its owning interpreter. // Ideally every object would know its owning interpreter.
// Until then, we have to rely on the caller to identify it // Until then, we have to rely on the caller to identify it
// (but we don't need it in all cases). // (but we don't need it in all cases).
_PyXIData_INTERPID(data) = (interp != NULL) _PyXIData_INTERPID(xidata) = (interp != NULL)
? PyInterpreterState_GetID(interp) ? PyInterpreterState_GetID(interp)
: -1; : -1;
data->new_object = new_object; xidata->new_object = new_object;
} }
int int
_PyXIData_InitWithSize(_PyXIData_t *data, _PyXIData_InitWithSize(_PyXIData_t *xidata,
PyInterpreterState *interp, PyInterpreterState *interp,
const size_t size, PyObject *obj, const size_t size, PyObject *obj,
xid_newobjfunc new_object) xid_newobjfunc new_object)
@ -153,50 +153,28 @@ _PyXIData_InitWithSize(_PyXIData_t *data,
// For now we always free the shared data in the same interpreter // For now we always free the shared data in the same interpreter
// where it was allocated, so the interpreter is required. // where it was allocated, so the interpreter is required.
assert(interp != NULL); assert(interp != NULL);
_PyXIData_Init(data, interp, NULL, obj, new_object); _PyXIData_Init(xidata, interp, NULL, obj, new_object);
data->data = PyMem_RawMalloc(size); xidata->data = PyMem_RawMalloc(size);
if (data->data == NULL) { if (xidata->data == NULL) {
return -1; return -1;
} }
data->free = PyMem_RawFree; xidata->free = PyMem_RawFree;
return 0; return 0;
} }
void 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. // This must be called in the owning interpreter.
assert(interp == NULL assert(interp == NULL
|| _PyXIData_INTERPID(data) == -1 || _PyXIData_INTERPID(xidata) == -1
|| _PyXIData_INTERPID(data) == PyInterpreterState_GetID(interp)); || _PyXIData_INTERPID(xidata) == PyInterpreterState_GetID(interp));
_xidata_clear(data); _xidata_clear(xidata);
} }
/* using cross-interpreter data */ /* getting 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;
}
static inline void static inline void
_set_xid_lookup_failure(PyThreadState *tstate, PyObject *obj, const char *msg, _set_xid_lookup_failure(PyThreadState *tstate, PyObject *obj, const char *msg,
@ -216,6 +194,7 @@ _set_xid_lookup_failure(PyThreadState *tstate, PyObject *obj, const char *msg,
} }
} }
int int
_PyObject_CheckXIData(PyThreadState *tstate, PyObject *obj) _PyObject_CheckXIData(PyThreadState *tstate, PyObject *obj)
{ {
@ -233,15 +212,39 @@ _PyObject_CheckXIData(PyThreadState *tstate, PyObject *obj)
return 0; 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 int
_PyObject_GetXIData(PyThreadState *tstate, _PyObject_GetXIData(PyThreadState *tstate,
PyObject *obj, _PyXIData_t *data) PyObject *obj, _PyXIData_t *xidata)
{ {
PyInterpreterState *interp = tstate->interp; PyInterpreterState *interp = tstate->interp;
// Reset data before re-populating. assert(xidata->data == NULL);
*data = (_PyXIData_t){0}; assert(xidata->obj == NULL);
_PyXIData_INTERPID(data) = -1; if (xidata->data != NULL || xidata->obj != NULL) {
_PyErr_SetString(tstate, PyExc_ValueError, "xidata not cleared");
}
// Call the "getdata" func for the object. // Call the "getdata" func for the object.
dlcontext_t ctx; dlcontext_t ctx;
@ -251,13 +254,18 @@ _PyObject_GetXIData(PyThreadState *tstate,
Py_INCREF(obj); Py_INCREF(obj);
xidatafunc getdata = lookup_getdata(&ctx, obj); xidatafunc getdata = lookup_getdata(&ctx, obj);
if (getdata == NULL) { if (getdata == NULL) {
if (PyErr_Occurred()) {
Py_DECREF(obj);
return -1;
}
// Fall back to obj
Py_DECREF(obj); Py_DECREF(obj);
if (!_PyErr_Occurred(tstate)) { if (!_PyErr_Occurred(tstate)) {
_set_xid_lookup_failure(tstate, obj, NULL, NULL); _set_xid_lookup_failure(tstate, obj, NULL, NULL);
} }
return -1; return -1;
} }
int res = getdata(tstate, obj, data); int res = getdata(tstate, obj, xidata);
Py_DECREF(obj); Py_DECREF(obj);
if (res != 0) { if (res != 0) {
PyObject *cause = _PyErr_GetRaisedException(tstate); PyObject *cause = _PyErr_GetRaisedException(tstate);
@ -268,19 +276,22 @@ _PyObject_GetXIData(PyThreadState *tstate,
} }
// Fill in the blanks and validate the result. // Fill in the blanks and validate the result.
_PyXIData_INTERPID(data) = PyInterpreterState_GetID(interp); _PyXIData_INTERPID(xidata) = PyInterpreterState_GetID(interp);
if (_check_xidata(tstate, data) != 0) { if (_check_xidata(tstate, xidata) != 0) {
(void)_PyXIData_Release(data); (void)_PyXIData_Release(xidata);
return -1; return -1;
} }
return 0; return 0;
} }
/* using cross-interpreter data */
PyObject * 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
@ -291,52 +302,52 @@ _call_clear_xidata(void *data)
} }
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! // Nothing to release!
if (rawfree) { if (rawfree) {
PyMem_RawFree(data); PyMem_RawFree(xidata);
} }
else { else {
data->data = NULL; xidata->data = NULL;
} }
return 0; return 0;
} }
// Switch to the original interpreter. // Switch to the original interpreter.
PyInterpreterState *interp = _PyInterpreterState_LookUpID( PyInterpreterState *interp = _PyInterpreterState_LookUpID(
_PyXIData_INTERPID(data)); _PyXIData_INTERPID(xidata));
if (interp == NULL) { if (interp == NULL) {
// The interpreter was already destroyed. // The interpreter was already destroyed.
// This function shouldn't have been called. // This function shouldn't have been called.
// XXX Someone leaked some memory... // XXX Someone leaked some memory...
assert(PyErr_Occurred()); assert(PyErr_Occurred());
if (rawfree) { if (rawfree) {
PyMem_RawFree(data); PyMem_RawFree(xidata);
} }
return -1; return -1;
} }
// "Release" the data and/or the object. // "Release" the data and/or the object.
if (rawfree) { if (rawfree) {
return _Py_CallInInterpreterAndRawFree(interp, _call_clear_xidata, data); return _Py_CallInInterpreterAndRawFree(interp, _call_clear_xidata, xidata);
} }
else { else {
return _Py_CallInInterpreter(interp, _call_clear_xidata, data); return _Py_CallInInterpreter(interp, _call_clear_xidata, xidata);
} }
} }
int int
_PyXIData_Release(_PyXIData_t *data) _PyXIData_Release(_PyXIData_t *xidata)
{ {
return _xidata_release(data, 0); return _xidata_release(xidata, 0);
} }
int int
_PyXIData_ReleaseAndRawFree(_PyXIData_t *data) _PyXIData_ReleaseAndRawFree(_PyXIData_t *xidata)
{ {
return _xidata_release(data, 1); return _xidata_release(xidata, 1);
} }
@ -455,15 +466,15 @@ _format_TracebackException(PyObject *tbexc)
static int static int
_release_xid_data(_PyXIData_t *data, int rawfree) _release_xid_data(_PyXIData_t *xidata, int rawfree)
{ {
PyObject *exc = PyErr_GetRaisedException(); PyObject *exc = PyErr_GetRaisedException();
int res = rawfree int res = rawfree
? _PyXIData_Release(data) ? _PyXIData_Release(xidata)
: _PyXIData_ReleaseAndRawFree(data); : _PyXIData_ReleaseAndRawFree(xidata);
if (res < 0) { if (res < 0) {
/* The owning interpreter is already destroyed. */ /* The owning interpreter is already destroyed. */
_PyXIData_Clear(NULL, data); _PyXIData_Clear(NULL, xidata);
// XXX Emit a warning? // XXX Emit a warning?
PyErr_Clear(); PyErr_Clear();
} }
@ -1107,7 +1118,7 @@ _PyXI_ApplyError(_PyXI_error *error)
typedef struct _sharednsitem { typedef struct _sharednsitem {
const char *name; const char *name;
_PyXIData_t *data; _PyXIData_t *xidata;
// We could have a "PyXIData _data" field, so it would // We could have a "PyXIData _data" field, so it would
// be allocated as part of the item and avoid an extra allocation. // be allocated as part of the item and avoid an extra allocation.
// However, doing so adds a bunch of complexity because we must // However, doing so adds a bunch of complexity because we must
@ -1132,7 +1143,7 @@ _sharednsitem_init(_PyXI_namespace_item *item, PyObject *key)
assert(!_sharednsitem_is_initialized(item)); assert(!_sharednsitem_is_initialized(item));
return -1; return -1;
} }
item->data = NULL; item->xidata = NULL;
assert(_sharednsitem_is_initialized(item)); assert(_sharednsitem_is_initialized(item));
return 0; return 0;
} }
@ -1140,11 +1151,11 @@ _sharednsitem_init(_PyXI_namespace_item *item, PyObject *key)
static int static int
_sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid) _sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid)
{ {
if (item->data == NULL) { if (item->xidata == NULL) {
return 0; return 0;
} }
if (p_interpid != NULL) { if (p_interpid != NULL) {
*p_interpid = _PyXIData_INTERPID(item->data); *p_interpid = _PyXIData_INTERPID(item->xidata);
} }
return 1; return 1;
} }
@ -1153,16 +1164,15 @@ static int
_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value) _sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
{ {
assert(_sharednsitem_is_initialized(item)); assert(_sharednsitem_is_initialized(item));
assert(item->data == NULL); assert(item->xidata == NULL);
item->data = PyMem_RawMalloc(sizeof(_PyXIData_t)); item->xidata = _PyXIData_New();
if (item->data == NULL) { if (item->xidata == NULL) {
PyErr_NoMemory();
return -1; return -1;
} }
PyThreadState *tstate = PyThreadState_Get(); PyThreadState *tstate = PyThreadState_Get();
if (_PyObject_GetXIData(tstate, value, item->data) != 0) { if (_PyObject_GetXIData(tstate, value, item->xidata) != 0) {
PyMem_RawFree(item->data); PyMem_RawFree(item->xidata);
item->data = NULL; item->xidata = NULL;
// The caller may want to propagate PyExc_NotShareableError // The caller may want to propagate PyExc_NotShareableError
// if currently switched between interpreters. // if currently switched between interpreters.
return -1; return -1;
@ -1173,11 +1183,11 @@ _sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
static void static void
_sharednsitem_clear_value(_PyXI_namespace_item *item) _sharednsitem_clear_value(_PyXI_namespace_item *item)
{ {
_PyXIData_t *data = item->data; _PyXIData_t *xidata = item->xidata;
if (data != NULL) { if (xidata != NULL) {
item->data = NULL; item->xidata = NULL;
int rawfree = 1; int rawfree = 1;
(void)_release_xid_data(data, rawfree); (void)_release_xid_data(xidata, rawfree);
} }
} }
@ -1195,7 +1205,7 @@ static int
_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns) _sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns)
{ {
assert(item->name != NULL); assert(item->name != NULL);
assert(item->data == NULL); assert(item->xidata == NULL);
PyObject *value = PyDict_GetItemString(ns, item->name); // borrowed PyObject *value = PyDict_GetItemString(ns, item->name); // borrowed
if (value == NULL) { if (value == NULL) {
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
@ -1218,8 +1228,8 @@ _sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
return -1; return -1;
} }
PyObject *value; PyObject *value;
if (item->data != NULL) { if (item->xidata != NULL) {
value = _PyXIData_NewObject(item->data); value = _PyXIData_NewObject(item->xidata);
if (value == NULL) { if (value == NULL) {
Py_DECREF(name); Py_DECREF(name);
return -1; return -1;

View file

@ -354,25 +354,25 @@ struct _shared_bytes_data {
}; };
static PyObject * 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); return PyBytes_FromStringAndSize(shared->bytes, shared->len);
} }
static int static int
_bytes_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data) _bytes_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{ {
if (_PyXIData_InitWithSize( if (_PyXIData_InitWithSize(
data, tstate->interp, sizeof(struct _shared_bytes_data), obj, xidata, tstate->interp, sizeof(struct _shared_bytes_data), obj,
_new_bytes_object _new_bytes_object
) < 0) ) < 0)
{ {
return -1; 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) { if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) {
_PyXIData_Clear(tstate->interp, data); _PyXIData_Clear(tstate->interp, xidata);
return -1; return -1;
} }
return 0; return 0;
@ -387,23 +387,23 @@ struct _shared_str_data {
}; };
static PyObject * 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); return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len);
} }
static int static int
_str_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data) _str_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{ {
if (_PyXIData_InitWithSize( if (_PyXIData_InitWithSize(
data, tstate->interp, sizeof(struct _shared_str_data), obj, xidata, tstate->interp, sizeof(struct _shared_str_data), obj,
_new_str_object _new_str_object
) < 0) ) < 0)
{ {
return -1; 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->kind = PyUnicode_KIND(obj);
shared->buffer = PyUnicode_DATA(obj); shared->buffer = PyUnicode_DATA(obj);
shared->len = PyUnicode_GET_LENGTH(obj); shared->len = PyUnicode_GET_LENGTH(obj);
@ -413,13 +413,13 @@ _str_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
// int // int
static PyObject * 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 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 /* Note that this means the size of shareable ints is bounded by
* sys.maxsize. Hence on 32-bit architectures that is half the * sys.maxsize. Hence on 32-bit architectures that is half the
@ -432,31 +432,31 @@ _long_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
} }
return -1; return -1;
} }
_PyXIData_Init(data, tstate->interp, (void *)value, NULL, _new_long_object); _PyXIData_Init(xidata, tstate->interp, (void *)value, NULL, _new_long_object);
// data->obj and data->free remain NULL // xidata->obj and xidata->free remain NULL
return 0; return 0;
} }
// float // float
static PyObject * 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); return PyFloat_FromDouble(*value_ptr);
} }
static int static int
_float_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data) _float_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
{ {
if (_PyXIData_InitWithSize( if (_PyXIData_InitWithSize(
data, tstate->interp, sizeof(double), NULL, xidata, tstate->interp, sizeof(double), NULL,
_new_float_object _new_float_object
) < 0) ) < 0)
{ {
return -1; return -1;
} }
double *shared = (double *)data->data; double *shared = (double *)xidata->data;
*shared = PyFloat_AsDouble(obj); *shared = PyFloat_AsDouble(obj);
return 0; return 0;
} }
@ -464,38 +464,38 @@ _float_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
// None // None
static PyObject * static PyObject *
_new_none_object(_PyXIData_t *data) _new_none_object(_PyXIData_t *xidata)
{ {
// XXX Singleton refcounts are problematic across interpreters... // XXX Singleton refcounts are problematic across interpreters...
return Py_NewRef(Py_None); return Py_NewRef(Py_None);
} }
static int 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); _PyXIData_Init(xidata, tstate->interp, NULL, NULL, _new_none_object);
// data->data, data->obj and data->free remain NULL // xidata->data, xidata->obj and xidata->free remain NULL
return 0; return 0;
} }
// bool // bool
static PyObject * 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_TRUE;
} }
Py_RETURN_FALSE; Py_RETURN_FALSE;
} }
static int 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, (void *) (Py_IsTrue(obj) ? (uintptr_t) 1 : (uintptr_t) 0), NULL,
_new_bool_object); _new_bool_object);
// data->obj and data->free remain NULL // xidata->obj and xidata->free remain NULL
return 0; return 0;
} }
@ -503,20 +503,20 @@ _bool_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
struct _shared_tuple_data { struct _shared_tuple_data {
Py_ssize_t len; Py_ssize_t len;
_PyXIData_t **data; _PyXIData_t **items;
}; };
static PyObject * 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); PyObject *tuple = PyTuple_New(shared->len);
if (tuple == NULL) { if (tuple == NULL) {
return NULL; return NULL;
} }
for (Py_ssize_t i = 0; i < shared->len; i++) { 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){ if (item == NULL){
Py_DECREF(tuple); Py_DECREF(tuple);
return NULL; return NULL;
@ -534,19 +534,19 @@ _tuple_shared_free(void* data)
int64_t interpid = PyInterpreterState_GetID(_PyInterpreterState_GET()); int64_t interpid = PyInterpreterState_GetID(_PyInterpreterState_GET());
#endif #endif
for (Py_ssize_t i = 0; i < shared->len; i++) { for (Py_ssize_t i = 0; i < shared->len; i++) {
if (shared->data[i] != NULL) { if (shared->items[i] != NULL) {
assert(_PyXIData_INTERPID(shared->data[i]) == interpid); assert(_PyXIData_INTERPID(shared->items[i]) == interpid);
_PyXIData_Release(shared->data[i]); _PyXIData_Release(shared->items[i]);
PyMem_RawFree(shared->data[i]); PyMem_RawFree(shared->items[i]);
shared->data[i] = NULL; shared->items[i] = NULL;
} }
} }
PyMem_Free(shared->data); PyMem_Free(shared->items);
PyMem_RawFree(shared); PyMem_RawFree(shared);
} }
static int 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); Py_ssize_t len = PyTuple_GET_SIZE(obj);
if (len < 0) { if (len < 0) {
@ -559,32 +559,32 @@ _tuple_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *data)
} }
shared->len = len; shared->len = len;
shared->data = (_PyXIData_t **) PyMem_Calloc(shared->len, sizeof(_PyXIData_t *)); shared->items = (_PyXIData_t **) PyMem_Calloc(shared->len, sizeof(_PyXIData_t *));
if (shared->data == NULL) { if (shared->items == NULL) {
PyErr_NoMemory(); PyErr_NoMemory();
return -1; return -1;
} }
for (Py_ssize_t i = 0; i < shared->len; i++) { for (Py_ssize_t i = 0; i < shared->len; i++) {
_PyXIData_t *data = _PyXIData_New(); _PyXIData_t *xidata_i = _PyXIData_New();
if (data == NULL) { if (xidata_i == NULL) {
goto error; // PyErr_NoMemory already set goto error; // PyErr_NoMemory already set
} }
PyObject *item = PyTuple_GET_ITEM(obj, i); PyObject *item = PyTuple_GET_ITEM(obj, i);
int res = -1; int res = -1;
if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) { if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) {
res = _PyObject_GetXIData(tstate, item, data); res = _PyObject_GetXIData(tstate, item, xidata_i);
_Py_LeaveRecursiveCallTstate(tstate); _Py_LeaveRecursiveCallTstate(tstate);
} }
if (res < 0) { if (res < 0) {
PyMem_RawFree(data); PyMem_RawFree(xidata_i);
goto error; goto error;
} }
shared->data[i] = data; shared->items[i] = xidata_i;
} }
_PyXIData_Init(data, tstate->interp, shared, obj, _new_tuple_object); _PyXIData_Init(xidata, tstate->interp, shared, obj, _new_tuple_object);
data->free = _tuple_shared_free; _PyXIData_SET_FREE(xidata, _tuple_shared_free);
return 0; return 0;
error: error: