mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
gh-101758: Add a Test For Single-Phase Init Module Variants (gh-101891)
The new test exercises the most important variants for single-phase init extension modules. We also add some explanation about those variants to import.c. https://github.com/python/cpython/issues/101758
This commit is contained in:
parent
81e3aa835c
commit
096d0097a0
3 changed files with 665 additions and 43 deletions
|
@ -5,74 +5,412 @@
|
|||
# define Py_BUILD_CORE_MODULE 1
|
||||
#endif
|
||||
|
||||
//#include <time.h>
|
||||
#include "Python.h"
|
||||
#include "pycore_namespace.h" // _PyNamespace_New()
|
||||
|
||||
|
||||
typedef struct {
|
||||
_PyTime_t initialized;
|
||||
PyObject *error;
|
||||
PyObject *int_const;
|
||||
PyObject *str_const;
|
||||
} module_state;
|
||||
|
||||
/* Process-global state is only used by _testsinglephase
|
||||
since it's the only one that does not support re-init. */
|
||||
static struct {
|
||||
int initialized_count;
|
||||
module_state module;
|
||||
} global_state = { .initialized_count = -1 };
|
||||
|
||||
static inline module_state *
|
||||
get_module_state(PyObject *module)
|
||||
{
|
||||
PyModuleDef *def = PyModule_GetDef(module);
|
||||
if (def->m_size == -1) {
|
||||
return &global_state.module;
|
||||
}
|
||||
else if (def->m_size == 0) {
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
module_state *state = (module_state*)PyModule_GetState(module);
|
||||
assert(state != NULL);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clear_state(module_state *state)
|
||||
{
|
||||
state->initialized = 0;
|
||||
Py_CLEAR(state->error);
|
||||
Py_CLEAR(state->int_const);
|
||||
Py_CLEAR(state->str_const);
|
||||
}
|
||||
|
||||
static int
|
||||
_set_initialized(_PyTime_t *initialized)
|
||||
{
|
||||
/* We go strictly monotonic to ensure each time is unique. */
|
||||
_PyTime_t prev;
|
||||
if (_PyTime_GetMonotonicClockWithInfo(&prev, NULL) != 0) {
|
||||
return -1;
|
||||
}
|
||||
/* We do a busy sleep since the interval should be super short. */
|
||||
_PyTime_t t;
|
||||
do {
|
||||
if (_PyTime_GetMonotonicClockWithInfo(&t, NULL) != 0) {
|
||||
return -1;
|
||||
}
|
||||
} while (t == prev);
|
||||
|
||||
*initialized = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_state(module_state *state)
|
||||
{
|
||||
assert(state->initialized == 0 &&
|
||||
state->error == NULL &&
|
||||
state->int_const == NULL &&
|
||||
state->str_const == NULL);
|
||||
|
||||
if (_set_initialized(&state->initialized) != 0) {
|
||||
goto error;
|
||||
}
|
||||
assert(state->initialized > 0);
|
||||
|
||||
/* Add an exception type */
|
||||
state->error = PyErr_NewException("_testsinglephase.error", NULL, NULL);
|
||||
if (state->error == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
state->int_const = PyLong_FromLong(1969);
|
||||
if (state->int_const == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
state->str_const = PyUnicode_FromString("something different");
|
||||
if (state->str_const == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
clear_state(state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
init_module(PyObject *module, module_state *state)
|
||||
{
|
||||
if (PyModule_AddObjectRef(module, "error", state->error) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (PyModule_AddObjectRef(module, "int_const", state->int_const) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (PyModule_AddObjectRef(module, "str_const", state->str_const) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
PyDoc_STRVAR(common_initialized_doc,
|
||||
"initialized()\n\
|
||||
\n\
|
||||
Return the seconds-since-epoch when the module was initialized.");
|
||||
|
||||
static PyObject *
|
||||
common_initialized(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
module_state *state = get_module_state(self);
|
||||
if (state == NULL) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
double d = _PyTime_AsSecondsDouble(state->initialized);
|
||||
return PyFloat_FromDouble(d);
|
||||
}
|
||||
|
||||
#define INITIALIZED_METHODDEF \
|
||||
{"initialized", common_initialized, METH_NOARGS, \
|
||||
common_initialized_doc}
|
||||
|
||||
|
||||
PyDoc_STRVAR(common_look_up_self_doc,
|
||||
"look_up_self()\n\
|
||||
\n\
|
||||
Return the module associated with this module's def.m_base.m_index.");
|
||||
|
||||
static PyObject *
|
||||
common_look_up_self(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
PyModuleDef *def = PyModule_GetDef(self);
|
||||
if (def == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return Py_NewRef(
|
||||
PyState_FindModule(def));
|
||||
}
|
||||
|
||||
#define LOOK_UP_SELF_METHODDEF \
|
||||
{"look_up_self", common_look_up_self, METH_NOARGS, common_look_up_self_doc}
|
||||
|
||||
|
||||
/* Function of two integers returning integer */
|
||||
|
||||
PyDoc_STRVAR(testexport_foo_doc,
|
||||
"foo(i,j)\n\
|
||||
PyDoc_STRVAR(common_sum_doc,
|
||||
"sum(i,j)\n\
|
||||
\n\
|
||||
Return the sum of i and j.");
|
||||
|
||||
static PyObject *
|
||||
testexport_foo(PyObject *self, PyObject *args)
|
||||
common_sum(PyObject *self, PyObject *args)
|
||||
{
|
||||
long i, j;
|
||||
long res;
|
||||
if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
|
||||
if (!PyArg_ParseTuple(args, "ll:sum", &i, &j))
|
||||
return NULL;
|
||||
res = i + j;
|
||||
return PyLong_FromLong(res);
|
||||
}
|
||||
|
||||
#define SUM_METHODDEF \
|
||||
{"sum", common_sum, METH_VARARGS, common_sum_doc}
|
||||
|
||||
static PyMethodDef TestMethods[] = {
|
||||
{"foo", testexport_foo, METH_VARARGS,
|
||||
testexport_foo_doc},
|
||||
{NULL, NULL} /* sentinel */
|
||||
|
||||
PyDoc_STRVAR(basic_initialized_count_doc,
|
||||
"initialized_count()\n\
|
||||
\n\
|
||||
Return how many times the module has been initialized.");
|
||||
|
||||
static PyObject *
|
||||
basic_initialized_count(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
assert(PyModule_GetDef(self)->m_size == -1);
|
||||
return PyLong_FromLong(global_state.initialized_count);
|
||||
}
|
||||
|
||||
#define INITIALIZED_COUNT_METHODDEF \
|
||||
{"initialized_count", basic_initialized_count, METH_VARARGS, \
|
||||
basic_initialized_count_doc}
|
||||
|
||||
|
||||
/*********************************************/
|
||||
/* the _testsinglephase module (and aliases) */
|
||||
/*********************************************/
|
||||
|
||||
/* This ia more typical of legacy extensions in the wild:
|
||||
- single-phase init
|
||||
- no module state
|
||||
- does not support repeated initialization
|
||||
(so m_copy is used)
|
||||
- the module def is cached in _PyRuntime.extensions
|
||||
(by name/filename)
|
||||
|
||||
Also note that, because the module has single-phase init,
|
||||
it is cached in interp->module_by_index (using mod->md_def->m_base.m_index).
|
||||
*/
|
||||
|
||||
static PyMethodDef TestMethods_Basic[] = {
|
||||
LOOK_UP_SELF_METHODDEF,
|
||||
SUM_METHODDEF,
|
||||
INITIALIZED_METHODDEF,
|
||||
INITIALIZED_COUNT_METHODDEF,
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
|
||||
static struct PyModuleDef _testsinglephase = {
|
||||
static struct PyModuleDef _testsinglephase_basic = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "_testsinglephase",
|
||||
.m_doc = PyDoc_STR("Test module _testsinglephase (main)"),
|
||||
.m_doc = PyDoc_STR("Test module _testsinglephase"),
|
||||
.m_size = -1, // no module state
|
||||
.m_methods = TestMethods,
|
||||
.m_methods = TestMethods_Basic,
|
||||
};
|
||||
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__testsinglephase(void)
|
||||
static PyObject *
|
||||
init__testsinglephase_basic(PyModuleDef *def)
|
||||
{
|
||||
PyObject *module = PyModule_Create(&_testsinglephase);
|
||||
if (global_state.initialized_count == -1) {
|
||||
global_state.initialized_count = 0;
|
||||
}
|
||||
|
||||
PyObject *module = PyModule_Create(def);
|
||||
if (module == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Add an exception type */
|
||||
PyObject *temp = PyErr_NewException("_testsinglephase.error", NULL, NULL);
|
||||
if (temp == NULL) {
|
||||
goto error;
|
||||
}
|
||||
if (PyModule_AddObject(module, "error", temp) != 0) {
|
||||
Py_DECREF(temp);
|
||||
goto error;
|
||||
module_state *state = &global_state.module;
|
||||
// It may have been set by a previous run or under a different name.
|
||||
clear_state(state);
|
||||
if (init_state(state) < 0) {
|
||||
Py_CLEAR(module);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyModule_AddIntConstant(module, "int_const", 1969) != 0) {
|
||||
goto error;
|
||||
if (init_module(module, state) < 0) {
|
||||
Py_CLEAR(module);
|
||||
goto finally;
|
||||
}
|
||||
|
||||
if (PyModule_AddStringConstant(module, "str_const", "something different") != 0) {
|
||||
goto error;
|
||||
}
|
||||
global_state.initialized_count++;
|
||||
|
||||
finally:
|
||||
return module;
|
||||
}
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__testsinglephase(void)
|
||||
{
|
||||
return init__testsinglephase_basic(&_testsinglephase_basic);
|
||||
}
|
||||
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__testsinglephase_basic_wrapper(void)
|
||||
{
|
||||
return PyInit__testsinglephase();
|
||||
}
|
||||
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__testsinglephase_basic_copy(void)
|
||||
{
|
||||
static struct PyModuleDef def = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "_testsinglephase_basic_copy",
|
||||
.m_doc = PyDoc_STR("Test module _testsinglephase_basic_copy"),
|
||||
.m_size = -1, // no module state
|
||||
.m_methods = TestMethods_Basic,
|
||||
};
|
||||
return init__testsinglephase_basic(&def);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************/
|
||||
/* the _testsinglephase_with_reinit module */
|
||||
/*******************************************/
|
||||
|
||||
/* This ia less typical of legacy extensions in the wild:
|
||||
- single-phase init (same as _testsinglephase above)
|
||||
- no module state
|
||||
- supports repeated initialization
|
||||
(so m_copy is not used)
|
||||
- the module def is not cached in _PyRuntime.extensions
|
||||
|
||||
At this point most modules would reach for multi-phase init (PEP 489).
|
||||
However, module state has been around a while (PEP 3121),
|
||||
and most extensions predate multi-phase init.
|
||||
|
||||
(This module is basically the same as _testsinglephase,
|
||||
but supports repeated initialization.)
|
||||
*/
|
||||
|
||||
static PyMethodDef TestMethods_Reinit[] = {
|
||||
LOOK_UP_SELF_METHODDEF,
|
||||
SUM_METHODDEF,
|
||||
INITIALIZED_METHODDEF,
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
static struct PyModuleDef _testsinglephase_with_reinit = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "_testsinglephase_with_reinit",
|
||||
.m_doc = PyDoc_STR("Test module _testsinglephase_with_reinit"),
|
||||
.m_size = 0,
|
||||
.m_methods = TestMethods_Reinit,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__testsinglephase_with_reinit(void)
|
||||
{
|
||||
/* We purposefully do not try PyState_FindModule() first here
|
||||
since we want to check the behavior of re-loading the module. */
|
||||
PyObject *module = PyModule_Create(&_testsinglephase_with_reinit);
|
||||
if (module == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(get_module_state(module) == NULL);
|
||||
|
||||
module_state state = {0};
|
||||
if (init_state(&state) < 0) {
|
||||
Py_CLEAR(module);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (init_module(module, &state) < 0) {
|
||||
Py_CLEAR(module);
|
||||
goto finally;
|
||||
}
|
||||
|
||||
finally:
|
||||
/* We only needed the module state for setting the module attrs. */
|
||||
clear_state(&state);
|
||||
return module;
|
||||
}
|
||||
|
||||
|
||||
/******************************************/
|
||||
/* the _testsinglephase_with_state module */
|
||||
/******************************************/
|
||||
|
||||
/* This ia less typical of legacy extensions in the wild:
|
||||
- single-phase init (same as _testsinglephase above)
|
||||
- has some module state
|
||||
- supports repeated initialization
|
||||
(so m_copy is not used)
|
||||
- the module def is not cached in _PyRuntime.extensions
|
||||
|
||||
At this point most modules would reach for multi-phase init (PEP 489).
|
||||
However, module state has been around a while (PEP 3121),
|
||||
and most extensions predate multi-phase init.
|
||||
*/
|
||||
|
||||
static PyMethodDef TestMethods_WithState[] = {
|
||||
LOOK_UP_SELF_METHODDEF,
|
||||
SUM_METHODDEF,
|
||||
INITIALIZED_METHODDEF,
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
static struct PyModuleDef _testsinglephase_with_state = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "_testsinglephase_with_state",
|
||||
.m_doc = PyDoc_STR("Test module _testsinglephase_with_state"),
|
||||
.m_size = sizeof(module_state),
|
||||
.m_methods = TestMethods_WithState,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__testsinglephase_with_state(void)
|
||||
{
|
||||
/* We purposefully do not try PyState_FindModule() first here
|
||||
since we want to check the behavior of re-loading the module. */
|
||||
PyObject *module = PyModule_Create(&_testsinglephase_with_state);
|
||||
if (module == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
module_state *state = get_module_state(module);
|
||||
assert(state != NULL);
|
||||
if (init_state(state) < 0) {
|
||||
Py_CLEAR(module);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (init_module(module, state) < 0) {
|
||||
clear_state(state);
|
||||
Py_CLEAR(module);
|
||||
goto finally;
|
||||
}
|
||||
|
||||
finally:
|
||||
return module;
|
||||
|
||||
error:
|
||||
Py_DECREF(module);
|
||||
return NULL;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue