Skip to content
Closed
23 changes: 18 additions & 5 deletions Include/datetime.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -186,18 +186,31 @@ typedef struct{
} PyDateTime_CAPI;

#define PyDateTime_CAPSULE_NAME "datetime.datetime_CAPI"

#define PyDateTime_INTERNAL_CAPSULE_NAME "datetime.datetime_CAPI_INTERNAL"

/* This block is only used as part of the public API and should not be
* included in _datetimemodule.c, which does not use the C API capsule.
* See bpo-35081 for more details.
* */
#ifndef _PY_DATETIME_IMPL
/* Define global variable for the C API and a macro for setting it. */
static PyDateTime_CAPI *PyDateTimeAPI = NULL;
static PyDateTime_CAPI *
_PyDateTimeAPI_not_ready(void)
{
return NULL;
}
static PyDateTime_CAPI *(*_PyDateTimeAPI_Get)(void) = _PyDateTimeAPI_not_ready;

static inline void
_PyDateTimeAPI_Import(void)
{
void *(*func)(void) = PyCapsule_Import(PyDateTime_INTERNAL_CAPSULE_NAME, 0);
if (func){
_PyDateTimeAPI_Get = func();
}
}

#define PyDateTime_IMPORT \
PyDateTimeAPI = (PyDateTime_CAPI *)PyCapsule_Import(PyDateTime_CAPSULE_NAME, 0)
#define PyDateTimeAPI _PyDateTimeAPI_Get()
#define PyDateTime_IMPORT _PyDateTimeAPI_Import()

/* Macro for access to the UTC singleton */
#define PyDateTime_TimeZone_UTC PyDateTimeAPI->TimeZone_UTC
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/datetimetester.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -91,7 +91,7 @@ def test_name_cleanup(self):
if not name.startswith('__') and not name.endswith('__'))
allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
'datetime_CAPI', 'time', 'timedelta', 'timezone',
'tzinfo', 'UTC', 'sys'])
'tzinfo', 'UTC', 'sys', 'datetime_CAPI_INTERNAL'])
self.assertEqual(names - allowed, set([]))

def test_divide_and_round(self):
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_capi/test_misc.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2282,6 +2282,23 @@ def test_module_state_shared_in_global(self):
subinterp_attr_id = os.read(r, 100)
self.assertEqual(main_attr_id, subinterp_attr_id)

@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
def test_datetime_capi_client(self):
script = textwrap.dedent("""
import importlib.machinery
import importlib.util
fullname = '_test_datetime_capi_client'
origin = importlib.util.find_spec('_testmultiphase').origin
loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
""")
exec(script) # run main interp first
exec(script) # run main interp twice
ret = support.run_in_subinterp(script)
self.assertEqual(ret, 0)


@requires_subinterpreters
class InterpreterConfigTests(unittest.TestCase):
Expand Down
59 changes: 59 additions & 0 deletions Modules/_datetimemodule.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,51 @@ static datetime_state _datetime_global_state;

#define STATIC_STATE() (&_datetime_global_state)

typedef struct{
PyInterpreterState *interp;
PyDateTime_CAPI *capi;
} CAPI_Cache;

static CAPI_Cache apicache[2];

static inline void
set_datetime_capi_by_interp(PyDateTime_CAPI *capi)
{
PyInterpreterState *interp = PyInterpreterState_Get();
int i = interp == PyInterpreterState_Main() ? 0 : 1;
apicache[i].interp = interp;
apicache[i].capi = capi;
}

static PyDateTime_CAPI *
_PyDateTimeAPI_Get(void)
{
PyInterpreterState *interp = PyInterpreterState_Get();
for (int i = 0; i < 2; i++){
if (apicache[i].interp == interp){
return apicache[i].capi;
}
}
PyDateTime_CAPI *capi = PyCapsule_Import(PyDateTime_CAPSULE_NAME, 0);
if (capi){
set_datetime_capi_by_interp(capi);
}
return capi;
}

static void *
_PyDateTimeAPI_Import(void)
{
PyDateTime_CAPI *capi = PyCapsule_Import(PyDateTime_CAPSULE_NAME, 0);
if (capi){
// PyInit__datetime() is not called when the module is already loaded
// with single-phase init.
set_datetime_capi_by_interp(capi);
return _PyDateTimeAPI_Get;
}
return NULL;
}

/* We require that C int be at least 32 bits, and use int virtually
* everywhere. In just a few cases we use a temp long, where a Python
* API returns a C long. In such cases, we have to ensure that the
Expand DownExpand Up@@ -6944,6 +6989,20 @@ _datetime_exec(PyObject *module)
goto error;
}

capsule = PyCapsule_New(_PyDateTimeAPI_Import,
PyDateTime_INTERNAL_CAPSULE_NAME, NULL);
if (capsule == NULL){
PyMem_Free(capi);
goto error;
}
if (PyModule_Add(module, "datetime_CAPI_INTERNAL", capsule) < 0){
PyMem_Free(capi);
goto error;
}

/* Ensure that the newest capi is used on multi-phase init */
set_datetime_capi_by_interp(capi);

/* A 4-year cycle has an extra leap day over what we'd get from
* pasting together 4 single years.
*/
Expand Down
78 changes: 78 additions & 0 deletions Modules/_testmultiphase.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -969,3 +969,81 @@ PyInit__test_shared_gil_only(void)
{
return PyModuleDef_Init(&shared_gil_only_def);
}


#include "datetime.h"

static int
datetime_capi_import_with_error(void)
{
static int is_datetime_multiphase = -1;
int ismain = PyInterpreterState_Get() == PyInterpreterState_Main();
if (ismain && is_datetime_multiphase < 0){
PyObject *module = PyImport_ImportModule("_datetime");
if (module == NULL){
return -1;
}
PyModuleDef *def = PyModule_GetDef(module);
Py_DECREF(module);
if (def && def->m_size >= 0){
is_datetime_multiphase = 1;
}
else{
is_datetime_multiphase = 0;
}
}
if (is_datetime_multiphase < 0){
PyErr_SetString(PyExc_AssertionError,
"Main interpreter must be loaded first.");
return -1;
}

_PyDateTimeAPI_Import();
if (!PyErr_Occurred()){
return 0;
}
#ifdef Py_GIL_DISABLED
if (!ismain && !is_datetime_multiphase){
// _datetime module and Capsule are not imported
PyErr_WriteUnraisable(NULL);
return 0;
}
#endif
return -1;
}

static int
datetime_capi_client_exec(PyObject *m)
{
_PyDateTimeAPI_Get = _PyDateTimeAPI_not_ready;
if (_PyDateTimeAPI_Get() != NULL){
PyErr_SetString(PyExc_AssertionError,
"DateTime API is expected to remain NULL.");
return -1;
}
if (datetime_capi_import_with_error() < 0){
return -1;
}
if (PyDateTimeAPI != PyCapsule_Import(PyDateTime_CAPSULE_NAME, 0)){
PyErr_SetString(PyExc_AssertionError,
"DateTime API does not match Capsule CAPI.");
return -1;
}
PyErr_Clear();
return 0;
}

static PyModuleDef_Slot datetime_capi_client_slots[] ={
{Py_mod_exec, datetime_capi_client_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{0, NULL},
};

static PyModuleDef datetime_capi_client_def = TEST_MODULE_DEF(
"_testmultiphase_datetime_capi_client", datetime_capi_client_slots, NULL);

PyMODINIT_FUNC
PyInit__test_datetime_capi_client(void)
{
return PyModuleDef_Init(&datetime_capi_client_def);
}
3 changes: 2 additions & 1 deletion Tools/c-analyzer/cpython/globals-to-fix.tsv
Original file line numberDiff line numberDiff line change
Expand Up@@ -450,7 +450,7 @@ Modules/_tkinter.c - trbInCmd -
## initialized once

## other
Include/datetime.h - PyDateTimeAPI -
Include/datetime.h - _PyDateTimeAPI_Get -
Modules/_ctypes/cfield.c _ctypes_get_fielddesc initialized -
Modules/_ctypes/malloc_closure.c - _pagesize -
Modules/_cursesmodule.c - initialised -
Expand All@@ -468,6 +468,7 @@ Modules/readline.c - libedit_history_start -
Modules/_ctypes/cfield.c - formattable -
Modules/_ctypes/malloc_closure.c - free_list -
Modules/_curses_panel.c - lop -
Modules/_datetimemodule.c - apicache -
Modules/_ssl/debughelpers.c _PySSL_keylog_callback lock -
Modules/_tkinter.c - quitMainLoop -
Modules/_tkinter.c - errorInCmd -
Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/ignored.tsv
Original file line numberDiff line numberDiff line change
Expand Up@@ -591,6 +591,7 @@ Modules/_testmultiphase.c - slots_exec_unreported_exception -
Modules/_testmultiphase.c - slots_nonmodule_with_exec_slots -
Modules/_testmultiphase.c - testexport_methods -
Modules/_testmultiphase.c - uninitialized_def -
Modules/_testmultiphase.c datetime_capi_import_with_error is_datetime_multiphase -
Modules/_testsinglephase.c - global_state -
Modules/_xxtestfuzz/_xxtestfuzz.c - _fuzzmodule -
Modules/_xxtestfuzz/_xxtestfuzz.c - module_methods -
Expand Down