mirror of
https://github.com/python/cpython.git
synced 2025-09-27 10:50:04 +00:00
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:
parent
b739ec5ab7
commit
6f04325992
10 changed files with 838 additions and 348 deletions
|
@ -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
|
||||||
|
|
230
Lib/test/_crossinterp_definitions.py
Normal file
230
Lib/test/_crossinterp_definitions.py
Normal 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
|
|
@ -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):
|
||||||
|
|
306
Lib/test/test_crossinterp.py
Normal file
306
Lib/test/test_crossinterp.py
Normal 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()
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue