summaryrefslogtreecommitdiffstats
path: root/Modules/_testsinglephase.c
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2023-02-14 21:26:03 (GMT)
committerGitHub <noreply@github.com>2023-02-14 21:26:03 (GMT)
commit096d0097a09e439a4564531b297a998e5d74c9b5 (patch)
treef526dc497006ca223952737f7103c103bf8bc0c3 /Modules/_testsinglephase.c
parent81e3aa835c32363f4547b6566edf1125386f1f6d (diff)
downloadcpython-096d0097a09e439a4564531b297a998e5d74c9b5.zip
cpython-096d0097a09e439a4564531b297a998e5d74c9b5.tar.gz
cpython-096d0097a09e439a4564531b297a998e5d74c9b5.tar.bz2
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
Diffstat (limited to 'Modules/_testsinglephase.c')
-rw-r--r--Modules/_testsinglephase.c394
1 files changed, 366 insertions, 28 deletions
diff --git a/Modules/_testsinglephase.c b/Modules/_testsinglephase.c
index 3bfe159..9e8dd64 100644
--- a/Modules/_testsinglephase.c
+++ b/Modules/_testsinglephase.c
@@ -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 struct PyModuleDef _testsinglephase = {
+static PyMethodDef TestMethods_Basic[] = {
+ LOOK_UP_SELF_METHODDEF,
+ SUM_METHODDEF,
+ INITIALIZED_METHODDEF,
+ INITIALIZED_COUNT_METHODDEF,
+ {NULL, NULL} /* sentinel */
+};
+
+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,
};
+static PyObject *
+init__testsinglephase_basic(PyModuleDef *def)
+{
+ if (global_state.initialized_count == -1) {
+ global_state.initialized_count = 0;
+ }
+
+ PyObject *module = PyModule_Create(def);
+ if (module == NULL) {
+ return NULL;
+ }
+
+ 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 (init_module(module, state) < 0) {
+ Py_CLEAR(module);
+ goto finally;
+ }
+
+ global_state.initialized_count++;
+
+finally:
+ return module;
+}
PyMODINIT_FUNC
PyInit__testsinglephase(void)
{
- PyObject *module = PyModule_Create(&_testsinglephase);
+ 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;
}
- /* Add an exception type */
- PyObject *temp = PyErr_NewException("_testsinglephase.error", NULL, NULL);
- if (temp == NULL) {
- goto error;
+ assert(get_module_state(module) == NULL);
+
+ module_state state = {0};
+ if (init_state(&state) < 0) {
+ Py_CLEAR(module);
+ return NULL;
}
- if (PyModule_AddObject(module, "error", temp) != 0) {
- Py_DECREF(temp);
- goto error;
+
+ if (init_module(module, &state) < 0) {
+ Py_CLEAR(module);
+ goto finally;
}
- if (PyModule_AddIntConstant(module, "int_const", 1969) != 0) {
- goto error;
+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;
}
- if (PyModule_AddStringConstant(module, "str_const", "something different") != 0) {
- goto error;
+ module_state *state = get_module_state(module);
+ assert(state != NULL);
+ if (init_state(state) < 0) {
+ Py_CLEAR(module);
+ return NULL;
}
- return module;
+ if (init_module(module, state) < 0) {
+ clear_state(state);
+ Py_CLEAR(module);
+ goto finally;
+ }
-error:
- Py_DECREF(module);
- return NULL;
+finally:
+ return module;
}