mirror of https://github.com/python/cpython
653 lines
18 KiB
C
653 lines
18 KiB
C
|
|
/* Testing module for single-phase initialization of extension modules
|
|
|
|
This file contains 5 distinct modules, meaning each as its own name
|
|
and its own init function (PyInit_...). The default import system will
|
|
only find the one matching the filename: _testsinglephase. To load the
|
|
others you must do so manually. For example:
|
|
|
|
```python
|
|
name = '_testsinglephase_base_wrapper'
|
|
filename = _testsinglephase.__file__
|
|
loader = importlib.machinery.ExtensionFileLoader(name, filename)
|
|
spec = importlib.util.spec_from_file_location(name, filename, loader=loader)
|
|
mod = importlib._bootstrap._load(spec)
|
|
```
|
|
|
|
Here are the 5 modules:
|
|
|
|
* _testsinglephase
|
|
* def: _testsinglephase_basic,
|
|
* m_name: "_testsinglephase"
|
|
* m_size: -1
|
|
* state
|
|
* process-global
|
|
* <int> initialized_count (default to -1; will never be 0)
|
|
* <module_state> module (see module state below)
|
|
* module state: no
|
|
* initial __dict__: see common initial __dict__ below
|
|
* init function
|
|
1. create module
|
|
2. clear <global>.module
|
|
3. initialize <global>.module: see module state below
|
|
4. initialize module: set initial __dict__
|
|
5. increment <global>.initialized_count
|
|
* functions
|
|
* (3 common, see below)
|
|
* initialized_count() - return <global>.module.initialized_count
|
|
* import system
|
|
* caches
|
|
* global extensions cache: yes
|
|
* def.m_base.m_copy: yes
|
|
* def.m_base.m_init: no
|
|
* per-interpreter cache: yes (all single-phase init modules)
|
|
* load in main interpreter
|
|
* initial (not already in global cache)
|
|
1. get init function from shared object file
|
|
2. run init function
|
|
3. copy __dict__ into def.m_base.m_copy
|
|
4. set entry in global cache
|
|
5. set entry in per-interpreter cache
|
|
6. set entry in sys.modules
|
|
* reload (already in sys.modules)
|
|
1. get def from global cache
|
|
2. get module from sys.modules
|
|
3. update module with contents of def.m_base.m_copy
|
|
* already loaded in other interpreter (already in global cache)
|
|
* same as reload, but create new module and update *it*
|
|
* not in any sys.modules, still in global cache
|
|
* same as already loaded
|
|
* load in legacy (non-isolated) interpreter
|
|
* same as main interpreter
|
|
* unload: never (all single-phase init modules)
|
|
* _testsinglephase_basic_wrapper
|
|
* identical to _testsinglephase except module name
|
|
* _testsinglephase_basic_copy
|
|
* def: static local variable in init function
|
|
* m_name: "_testsinglephase_basic_copy"
|
|
* m_size: -1
|
|
* state: same as _testsinglephase
|
|
* init function: same as _testsinglephase
|
|
* functions: same as _testsinglephase
|
|
* import system: same as _testsinglephase
|
|
* _testsinglephase_with_reinit
|
|
* def: _testsinglephase_with_reinit,
|
|
* m_name: "_testsinglephase_with_reinit"
|
|
* m_size: 0
|
|
* state
|
|
* process-global state: no
|
|
* module state: no
|
|
* initial __dict__: see common initial __dict__ below
|
|
* init function
|
|
1. create module
|
|
2. initialize temporary module state (local var): see module state below
|
|
3. initialize module: set initial __dict__
|
|
* functions: see common functions below
|
|
* import system
|
|
* caches
|
|
* global extensions cache: only if loaded in main interpreter
|
|
* def.m_base.m_copy: no
|
|
* def.m_base.m_init: only if loaded in the main interpreter
|
|
* per-interpreter cache: yes (all single-phase init modules)
|
|
* load in main interpreter
|
|
* initial (not already in global cache)
|
|
* (same as _testsinglephase except step 3)
|
|
1. get init function from shared object file
|
|
2. run init function
|
|
3. set def.m_base.m_init to the init function
|
|
4. set entry in global cache
|
|
5. set entry in per-interpreter cache
|
|
6. set entry in sys.modules
|
|
* reload (already in sys.modules)
|
|
1. get def from global cache
|
|
2. call def->m_base.m_init to get a new module object
|
|
3. replace the existing module in sys.modules
|
|
* already loaded in other interpreter (already in global cache)
|
|
* same as reload (since will only be in cache for main interp)
|
|
* not in any sys.modules, still in global cache
|
|
* same as already loaded
|
|
* load in legacy (non-isolated) interpreter
|
|
* initial (not already in global cache)
|
|
* (same as main interpreter except skip steps 3 & 4 there)
|
|
1. get init function from shared object file
|
|
2. run init function
|
|
...
|
|
5. set entry in per-interpreter cache
|
|
6. set entry in sys.modules
|
|
* reload (already in sys.modules)
|
|
* same as initial (load from scratch)
|
|
* already loaded in other interpreter (already in global cache)
|
|
* same as initial (load from scratch)
|
|
* not in any sys.modules, still in global cache
|
|
* same as initial (load from scratch)
|
|
* unload: never (all single-phase init modules)
|
|
* _testsinglephase_with_state
|
|
* def: _testsinglephase_with_state,
|
|
* m_name: "_testsinglephase_with_state"
|
|
* m_size: sizeof(module_state)
|
|
* state
|
|
* process-global: no
|
|
* module state: see module state below
|
|
* initial __dict__: see common initial __dict__ below
|
|
* init function
|
|
1. create module
|
|
3. initialize module state: see module state below
|
|
4. initialize module: set initial __dict__
|
|
5. increment <global>.initialized_count
|
|
* functions: see common functions below
|
|
* import system: same as _testsinglephase_basic_copy
|
|
|
|
Module state:
|
|
|
|
* fields
|
|
* <PyTime_t> initialized - when the module was first initialized
|
|
* <PyObject> *error
|
|
* <PyObject> *int_const
|
|
* <PyObject> *str_const
|
|
* initialization
|
|
1. set state.initialized to the current time
|
|
2. set state.error to a new exception class
|
|
3. set state->int_const to int(1969)
|
|
4. set state->str_const to "something different"
|
|
|
|
Common initial __dict__:
|
|
|
|
* error: state.error
|
|
* int_const: state.int_const
|
|
* str_const: state.str_const
|
|
* _module_initialized: state.initialized
|
|
|
|
Common functions:
|
|
|
|
* look_up_self() - return the module from the per-interpreter "by-index" cache
|
|
* sum() - return a + b
|
|
* state_initialized() - return state->initialized (or None if m_size == 0)
|
|
|
|
See Python/import.c, especially the long comments, for more about
|
|
single-phase init modules.
|
|
*/
|
|
|
|
#ifndef Py_BUILD_CORE_BUILTIN
|
|
# 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 = {
|
|
|
|
#define NOT_INITIALIZED -1
|
|
.initialized_count = NOT_INITIALIZED,
|
|
};
|
|
|
|
static void clear_state(module_state *state);
|
|
|
|
static void
|
|
clear_global_state(void)
|
|
{
|
|
clear_state(&global_state.module);
|
|
global_state.initialized_count = NOT_INITIALIZED;
|
|
}
|
|
|
|
|
|
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_Monotonic(&prev) != 0) {
|
|
return -1;
|
|
}
|
|
/* We do a busy sleep since the interval should be super short. */
|
|
PyTime_t t;
|
|
do {
|
|
if (PyTime_Monotonic(&t) != 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;
|
|
}
|
|
|
|
double d = PyTime_AsSecondsDouble(state->initialized);
|
|
if (PyModule_Add(module, "_module_initialized", PyFloat_FromDouble(d)) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
PyDoc_STRVAR(common_state_initialized_doc,
|
|
"state_initialized()\n\
|
|
\n\
|
|
Return the seconds-since-epoch when the module state was initialized.");
|
|
|
|
static PyObject *
|
|
common_state_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 STATE_INITIALIZED_METHODDEF \
|
|
{"state_initialized", common_state_initialized, METH_NOARGS, \
|
|
common_state_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(common_sum_doc,
|
|
"sum(i,j)\n\
|
|
\n\
|
|
Return the sum of i and j.");
|
|
|
|
static PyObject *
|
|
common_sum(PyObject *self, PyObject *args)
|
|
{
|
|
long i, j;
|
|
long res;
|
|
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}
|
|
|
|
|
|
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_NOARGS, \
|
|
basic_initialized_count_doc}
|
|
|
|
|
|
PyDoc_STRVAR(basic__clear_globals_doc,
|
|
"_clear_globals()\n\
|
|
\n\
|
|
Free all global state and set it to uninitialized.");
|
|
|
|
static PyObject *
|
|
basic__clear_globals(PyObject *self, PyObject *Py_UNUSED(ignored))
|
|
{
|
|
assert(PyModule_GetDef(self)->m_size == -1);
|
|
clear_global_state();
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
#define _CLEAR_GLOBALS_METHODDEF \
|
|
{"_clear_globals", basic__clear_globals, METH_NOARGS, \
|
|
basic__clear_globals_doc}
|
|
|
|
|
|
PyDoc_STRVAR(basic__clear_module_state_doc, "_clear_module_state()\n\
|
|
\n\
|
|
Free the module state and set it to uninitialized.");
|
|
|
|
static PyObject *
|
|
basic__clear_module_state(PyObject *self, PyObject *Py_UNUSED(ignored))
|
|
{
|
|
module_state *state = get_module_state(self);
|
|
if (state != NULL) {
|
|
clear_state(state);
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
#define _CLEAR_MODULE_STATE_METHODDEF \
|
|
{"_clear_module_state", basic__clear_module_state, METH_NOARGS, \
|
|
basic__clear_module_state_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,
|
|
STATE_INITIALIZED_METHODDEF,
|
|
INITIALIZED_COUNT_METHODDEF,
|
|
_CLEAR_GLOBALS_METHODDEF,
|
|
{NULL, NULL} /* sentinel */
|
|
};
|
|
|
|
static struct PyModuleDef _testsinglephase_basic = {
|
|
PyModuleDef_HEAD_INIT,
|
|
.m_name = "_testsinglephase",
|
|
.m_doc = PyDoc_STR("Test module _testsinglephase"),
|
|
.m_size = -1, // no module state
|
|
.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;
|
|
}
|
|
#ifdef Py_GIL_DISABLED
|
|
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
|
|
#endif
|
|
|
|
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)
|
|
{
|
|
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,
|
|
STATE_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;
|
|
}
|
|
#ifdef Py_GIL_DISABLED
|
|
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
|
|
#endif
|
|
|
|
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 is 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,
|
|
STATE_INITIALIZED_METHODDEF,
|
|
_CLEAR_MODULE_STATE_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;
|
|
}
|
|
#ifdef Py_GIL_DISABLED
|
|
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
|
|
#endif
|
|
|
|
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;
|
|
}
|