Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Include/internal/pycore_crossinterp.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ PyAPI_FUNC(int) _PyCode_GetPureScriptXIData(
PyObject *,
_PyXIData_t *);

// _PyObject_GetXIData() for functions
PyAPI_FUNC(PyObject *) _PyFunction_FromXIData(_PyXIData_t *);
PyAPI_FUNC(int) _PyFunction_GetXIData(
PyThreadState *,
PyObject *,
_PyXIData_t *);


/* using cross-interpreter data */

Expand Down
34 changes: 34 additions & 0 deletions Lib/test/test_crossinterp.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,40 @@ def test_other_objects(self):
])


class ShareableFuncTests(_GetXIDataTests):

MODE = 'func'

def test_stateless(self):
self.assert_roundtrip_not_equal([
*defs.STATELESS_FUNCTIONS,
# Generators can be stateless too.
*defs.FUNCTION_LIKE,
])

def test_not_stateless(self):
self.assert_not_shareable([
*(f for f in defs.FUNCTIONS
if f not in defs.STATELESS_FUNCTIONS),
])

def test_other_objects(self):
self.assert_not_shareable([
None,
True,
False,
Ellipsis,
NotImplemented,
9999,
'spam',
b'spam',
(),
[],
{},
object(),
])


class PureShareableScriptTests(_GetXIDataTests):

MODE = 'script-pure'
Expand Down
5 changes: 5 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1989,6 +1989,11 @@ get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs)
goto error;
}
}
else if (strcmp(mode, "func") == 0) {
if (_PyFunction_GetXIData(tstate, obj, xidata) != 0) {
goto error;
}
}
else if (strcmp(mode, "script") == 0) {
if (_PyCode_GetScriptXIData(tstate, obj, xidata) != 0) {
goto error;
Expand Down
1 change: 1 addition & 0 deletions Python/crossinterp.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_namespace.h" // _PyNamespace_New()
#include "pycore_pythonrun.h" // _Py_SourceAsString()
#include "pycore_setobject.h" // _PySet_NextEntry()
#include "pycore_typeobject.h" // _PyStaticType_InitBuiltin()


Expand Down
56 changes: 56 additions & 0 deletions Python/crossinterp_data_lookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,60 @@ _PyCode_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
return 0;
}

// function

PyObject *
_PyFunction_FromXIData(_PyXIData_t *xidata)
{
// For now "stateless" functions are the only ones we must accommodate.

PyObject *code = _PyMarshal_ReadObjectFromXIData(xidata);
if (code == NULL) {
return NULL;
}
// Create a new function.
assert(PyCode_Check(code));
PyObject *globals = PyDict_New();
if (globals == NULL) {
Py_DECREF(code);
return NULL;
}
PyObject *func = PyFunction_New(code, globals);
Py_DECREF(code);
Py_DECREF(globals);
return func;
}

int
_PyFunction_GetXIData(PyThreadState *tstate, PyObject *func,
_PyXIData_t *xidata)
{
if (!PyFunction_Check(func)) {
const char *msg = "expected a function, got %R";
format_notshareableerror(tstate, NULL, 0, msg, func);
return -1;
}
if (_PyFunction_VerifyStateless(tstate, func) < 0) {
PyObject *cause = _PyErr_GetRaisedException(tstate);
assert(cause != NULL);
const char *msg = "non-stateless functions are not shareable";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not confusing, but the triple negation (non, less, not) looks a bit redundant. I do not think NotShareableError needs "not shareable" in the message.

set_notshareableerror(tstate, cause, 0, msg);
Py_DECREF(cause);
return -1;
}
PyObject *code = PyFunction_GET_CODE(func);

// Ideally code objects would be immortal and directly shareable.
// In the meantime, we use marshal.
if (_PyMarshal_GetXIData(tstate, code, xidata) < 0) {
return -1;
}
// Replace _PyMarshal_ReadObjectFromXIData.
// (_PyFunction_FromXIData() will call it.)
_PyXIData_SET_NEW_OBJECT(xidata, _PyFunction_FromXIData);
return 0;
}


// registration

Expand Down Expand Up @@ -717,4 +771,6 @@ _register_builtins_for_crossinterpreter_data(dlregistry_t *xidregistry)
if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) {
Py_FatalError("could not register tuple for cross-interpreter sharing");
}

// For now, we do not register PyCode_Type or PyFunction_Type.
}
Loading