gh-117398: Add datetime Module State (gh-119810)

I was able to make use of the existing datetime_state struct, but there was one tricky thing I had to sort out.  We mostly aren't converting to heap types, so we can't use things like PyType_GetModuleByDef() to look up the module state.  The solution I came up with is somewhat novel, but I consider it straightforward.  Also, it shouldn't have much impact on performance.

In summary, this main changes here are:

* I've added some macros to help hide how various objects relate to module state
* as a solution to the module state lookup problem, I've stored the last loaded module on the current interpreter's internal dict (actually a weakref)
* if the static type method is used after the module has been deleted, it is reloaded
* to avoid extra work when loading the module, we directly copy the objects (new refs only) from the old module state into the new state if the old module hasn't been deleted yet
* during module init we set various objects on the static types' __dict__s; to simplify things, we only do that the first time; once those static types have a separate __dict__ per interpreter, we'll do it every time
* we now clear the module state when the module is destroyed (before, we were leaking everything in _datetime_global_state)
This commit is contained in:
Eric Snow 2024-06-03 17:56:00 -04:00 committed by GitHub
parent 47fb4327b5
commit d82a7ba041
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 375 additions and 161 deletions

View File

@ -829,6 +829,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(c_call));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(c_exception));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(c_return));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cached_datetime_module));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cached_statements));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cadata));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cafile));

View File

@ -318,6 +318,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(c_call)
STRUCT_FOR_ID(c_exception)
STRUCT_FOR_ID(c_return)
STRUCT_FOR_ID(cached_datetime_module)
STRUCT_FOR_ID(cached_statements)
STRUCT_FOR_ID(cadata)
STRUCT_FOR_ID(cafile)

View File

@ -827,6 +827,7 @@ extern "C" {
INIT_ID(c_call), \
INIT_ID(c_exception), \
INIT_ID(c_return), \
INIT_ID(cached_datetime_module), \
INIT_ID(cached_statements), \
INIT_ID(cadata), \
INIT_ID(cafile), \

View File

@ -795,6 +795,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
string = &_Py_ID(c_return);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(cached_datetime_module);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);
string = &_Py_ID(cached_statements);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternInPlace(interp, &string);

View File

@ -25,17 +25,18 @@
# include <winsock2.h> /* struct timeval */
#endif
typedef struct {
/* Static types exposed by the datetime C-API. */
PyTypeObject *date_type;
PyTypeObject *datetime_type;
PyTypeObject *delta_type;
PyTypeObject *time_type;
PyTypeObject *tzinfo_type;
/* Exposed indirectly via TimeZone_UTC. */
PyTypeObject *timezone_type;
/* Other module classes. */
/* forward declarations */
static PyTypeObject PyDateTime_DateType;
static PyTypeObject PyDateTime_DateTimeType;
static PyTypeObject PyDateTime_TimeType;
static PyTypeObject PyDateTime_DeltaType;
static PyTypeObject PyDateTime_TZInfoType;
static PyTypeObject PyDateTime_TimeZoneType;
typedef struct {
/* Module heap types. */
PyTypeObject *isocalendar_date_type;
/* Conversion factors. */
@ -47,39 +48,182 @@ typedef struct {
PyObject *us_per_week; // 1e6 * 3600 * 24 * 7 as Python int
PyObject *seconds_per_day; // 3600 * 24 as Python int
/* The interned UTC timezone instance */
PyObject *utc;
/* The interned Unix epoch datetime instance */
PyObject *epoch;
/* While we use a global state, we ensure it's only initialized once */
int initialized;
} datetime_state;
static datetime_state _datetime_global_state;
/* The module has a fixed number of static objects, due to being exposed
* through the datetime C-API. There are five types exposed directly,
* one type exposed indirectly, and one singleton constant (UTC).
*
* Each of these objects is hidden behind a macro in the same way as
* the per-module objects stored in module state. The macros for the
* static objects don't need to be passed a state, but the consistency
* of doing so is more clear. We use a dedicated noop macro, NO_STATE,
* to make the special case obvious. */
static inline datetime_state* get_datetime_state(void)
#define NO_STATE NULL
#define DATE_TYPE(st) &PyDateTime_DateType
#define DATETIME_TYPE(st) &PyDateTime_DateTimeType
#define TIME_TYPE(st) &PyDateTime_TimeType
#define DELTA_TYPE(st) &PyDateTime_DeltaType
#define TZINFO_TYPE(st) &PyDateTime_TZInfoType
#define TIMEZONE_TYPE(st) &PyDateTime_TimeZoneType
#define ISOCALENDAR_DATE_TYPE(st) st->isocalendar_date_type
#define PyDate_Check(op) PyObject_TypeCheck(op, DATE_TYPE(NO_STATE))
#define PyDate_CheckExact(op) Py_IS_TYPE(op, DATE_TYPE(NO_STATE))
#define PyDateTime_Check(op) PyObject_TypeCheck(op, DATETIME_TYPE(NO_STATE))
#define PyDateTime_CheckExact(op) Py_IS_TYPE(op, DATETIME_TYPE(NO_STATE))
#define PyTime_Check(op) PyObject_TypeCheck(op, TIME_TYPE(NO_STATE))
#define PyTime_CheckExact(op) Py_IS_TYPE(op, TIME_TYPE(NO_STATE))
#define PyDelta_Check(op) PyObject_TypeCheck(op, DELTA_TYPE(NO_STATE))
#define PyDelta_CheckExact(op) Py_IS_TYPE(op, DELTA_TYPE(NO_STATE))
#define PyTZInfo_Check(op) PyObject_TypeCheck(op, TZINFO_TYPE(NO_STATE))
#define PyTZInfo_CheckExact(op) Py_IS_TYPE(op, TZINFO_TYPE(NO_STATE))
#define PyTimezone_Check(op) PyObject_TypeCheck(op, TIMEZONE_TYPE(NO_STATE))
#define CONST_US_PER_MS(st) st->us_per_ms
#define CONST_US_PER_SECOND(st) st->us_per_second
#define CONST_US_PER_MINUTE(st) st->us_per_minute
#define CONST_US_PER_HOUR(st) st->us_per_hour
#define CONST_US_PER_DAY(st) st->us_per_day
#define CONST_US_PER_WEEK(st) st->us_per_week
#define CONST_SEC_PER_DAY(st) st->seconds_per_day
#define CONST_EPOCH(st) st->epoch
#define CONST_UTC(st) ((PyObject *)&utc_timezone)
static datetime_state *
get_module_state(PyObject *module)
{
return &_datetime_global_state;
void *state = _PyModule_GetState(module);
assert(state != NULL);
return (datetime_state *)state;
}
#define PyDate_Check(op) PyObject_TypeCheck(op, get_datetime_state()->date_type)
#define PyDate_CheckExact(op) Py_IS_TYPE(op, get_datetime_state()->date_type)
#define PyDateTime_Check(op) PyObject_TypeCheck(op, get_datetime_state()->datetime_type)
#define PyDateTime_CheckExact(op) Py_IS_TYPE(op, get_datetime_state()->datetime_type)
#define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module))
#define PyTime_Check(op) PyObject_TypeCheck(op, get_datetime_state()->time_type)
#define PyTime_CheckExact(op) Py_IS_TYPE(op, get_datetime_state()->time_type)
static PyObject *
get_current_module(PyInterpreterState *interp)
{
PyObject *dict = PyInterpreterState_GetDict(interp);
if (dict == NULL) {
return NULL;
}
PyObject *ref = NULL;
if (PyDict_GetItemRef(dict, INTERP_KEY, &ref) < 0) {
return NULL;
}
if (ref == NULL) {
return NULL;
}
PyObject *mod = NULL;
(void)PyWeakref_GetRef(ref, &mod);
if (mod == Py_None) {
Py_CLEAR(mod);
}
Py_DECREF(ref);
return mod;
}
#define PyDelta_Check(op) PyObject_TypeCheck(op, get_datetime_state()->delta_type)
#define PyDelta_CheckExact(op) Py_IS_TYPE(op, get_datetime_state()->delta_type)
static PyModuleDef datetimemodule;
#define PyTZInfo_Check(op) PyObject_TypeCheck(op, get_datetime_state()->tzinfo_type)
#define PyTZInfo_CheckExact(op) Py_IS_TYPE(op, get_datetime_state()->tzinfo_type)
static datetime_state *
_get_current_state(PyObject **p_mod)
{
PyInterpreterState *interp = PyInterpreterState_Get();
PyObject *mod = get_current_module(interp);
if (mod == NULL) {
assert(!PyErr_Occurred());
if (PyErr_Occurred()) {
return NULL;
}
/* The static types can outlive the module,
* so we must re-import the module. */
mod = PyImport_ImportModule("_datetime");
if (mod == NULL) {
return NULL;
}
}
datetime_state *st = get_module_state(mod);
*p_mod = mod;
return st;
}
#define GET_CURRENT_STATE(MOD_VAR) \
_get_current_state(&MOD_VAR)
#define RELEASE_CURRENT_STATE(ST_VAR, MOD_VAR) \
Py_DECREF(MOD_VAR)
static int
set_current_module(PyInterpreterState *interp, PyObject *mod)
{
assert(mod != NULL);
PyObject *dict = PyInterpreterState_GetDict(interp);
if (dict == NULL) {
return -1;
}
PyObject *ref = PyWeakref_NewRef(mod, NULL);
if (ref == NULL) {
return -1;
}
int rc = PyDict_SetItem(dict, INTERP_KEY, ref);
Py_DECREF(ref);
return rc;
}
static void
clear_current_module(PyInterpreterState *interp, PyObject *expected)
{
PyObject *exc = PyErr_GetRaisedException();
PyObject *current = NULL;
PyObject *dict = PyInterpreterState_GetDict(interp);
if (dict == NULL) {
goto error;
}
if (expected != NULL) {
PyObject *ref = NULL;
if (PyDict_GetItemRef(dict, INTERP_KEY, &ref) < 0) {
goto error;
}
if (ref != NULL) {
int rc = PyWeakref_GetRef(ref, &current);
Py_DECREF(ref);
if (rc < 0) {
goto error;
}
if (current != expected) {
goto finally;
}
}
}
if (PyDict_DelItem(dict, INTERP_KEY) < 0) {
if (!PyErr_ExceptionMatches(PyExc_KeyError)) {
goto error;
}
}
goto finally;
error:
PyErr_Print();
finally:
Py_XDECREF(current);
PyErr_SetRaisedException(exc);
}
#define PyTimezone_Check(op) PyObject_TypeCheck(op, get_datetime_state()->timezone_type)
/* 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
@ -988,7 +1132,7 @@ new_date_ex(int year, int month, int day, PyTypeObject *type)
}
#define new_date(year, month, day) \
new_date_ex(year, month, day, get_datetime_state()->date_type)
new_date_ex(year, month, day, DATE_TYPE(NO_STATE))
// Forward declaration
static PyObject *
@ -998,13 +1142,12 @@ new_datetime_ex(int, int, int, int, int, int, int, PyObject *, PyTypeObject *);
static PyObject *
new_date_subclass_ex(int year, int month, int day, PyObject *cls)
{
datetime_state *st = get_datetime_state();
PyObject *result;
// We have "fast path" constructors for two subclasses: date and datetime
if ((PyTypeObject *)cls == st->date_type) {
if ((PyTypeObject *)cls == DATE_TYPE(NO_STATE)) {
result = new_date_ex(year, month, day, (PyTypeObject *)cls);
}
else if ((PyTypeObject *)cls == st->datetime_type) {
else if ((PyTypeObject *)cls == DATETIME_TYPE(NO_STATE)) {
result = new_datetime_ex(year, month, day, 0, 0, 0, 0, Py_None,
(PyTypeObject *)cls);
}
@ -1058,8 +1201,7 @@ new_datetime_ex(int year, int month, int day, int hour, int minute,
}
#define new_datetime(y, m, d, hh, mm, ss, us, tzinfo, fold) \
new_datetime_ex2(y, m, d, hh, mm, ss, us, tzinfo, fold, \
get_datetime_state()->datetime_type)
new_datetime_ex2(y, m, d, hh, mm, ss, us, tzinfo, fold, DATETIME_TYPE(NO_STATE))
static PyObject *
call_subclass_fold(PyObject *cls, int fold, const char *format, ...)
@ -1100,9 +1242,8 @@ new_datetime_subclass_fold_ex(int year, int month, int day, int hour, int minute
int second, int usecond, PyObject *tzinfo,
int fold, PyObject *cls)
{
datetime_state *st = get_datetime_state();
PyObject* dt;
if ((PyTypeObject*)cls == st->datetime_type) {
if ((PyTypeObject*)cls == DATETIME_TYPE(NO_STATE)) {
// Use the fast path constructor
dt = new_datetime(year, month, day, hour, minute, second, usecond,
tzinfo, fold);
@ -1163,16 +1304,15 @@ new_time_ex(int hour, int minute, int second, int usecond,
return new_time_ex2(hour, minute, second, usecond, tzinfo, 0, type);
}
#define new_time(hh, mm, ss, us, tzinfo, fold) \
new_time_ex2(hh, mm, ss, us, tzinfo, fold, get_datetime_state()->time_type)
#define new_time(hh, mm, ss, us, tzinfo, fold) \
new_time_ex2(hh, mm, ss, us, tzinfo, fold, TIME_TYPE(NO_STATE))
static PyObject *
new_time_subclass_fold_ex(int hour, int minute, int second, int usecond,
PyObject *tzinfo, int fold, PyObject *cls)
{
PyObject *t;
datetime_state *st = get_datetime_state();
if ((PyTypeObject*)cls == st->time_type) {
if ((PyTypeObject*)cls == TIME_TYPE(NO_STATE)) {
// Use the fast path constructor
t = new_time(hour, minute, second, usecond, tzinfo, fold);
}
@ -1224,7 +1364,7 @@ new_delta_ex(int days, int seconds, int microseconds, int normalize,
}
#define new_delta(d, s, us, normalize) \
new_delta_ex(d, s, us, normalize, get_datetime_state()->delta_type)
new_delta_ex(d, s, us, normalize, DELTA_TYPE(NO_STATE))
typedef struct
@ -1244,8 +1384,7 @@ static PyObject *
create_timezone(PyObject *offset, PyObject *name)
{
PyDateTime_TimeZone *self;
datetime_state *st = get_datetime_state();
PyTypeObject *type = st->timezone_type;
PyTypeObject *type = TIMEZONE_TYPE(NO_STATE);
assert(offset != NULL);
assert(PyDelta_Check(offset));
@ -1267,6 +1406,7 @@ create_timezone(PyObject *offset, PyObject *name)
}
static int delta_bool(PyDateTime_Delta *self);
static PyDateTime_TimeZone utc_timezone;
static PyObject *
new_timezone(PyObject *offset, PyObject *name)
@ -1276,8 +1416,7 @@ new_timezone(PyObject *offset, PyObject *name)
assert(name == NULL || PyUnicode_Check(name));
if (name == NULL && delta_bool((PyDateTime_Delta *)offset) == 0) {
datetime_state *st = get_datetime_state();
return Py_NewRef(st->utc);
return Py_NewRef(CONST_UTC(NO_STATE));
}
if ((GET_TD_DAYS(offset) == -1 &&
GET_TD_SECONDS(offset) == 0 &&
@ -1490,8 +1629,7 @@ tzinfo_from_isoformat_results(int rv, int tzoffset, int tz_useconds)
if (rv == 1) {
// Create a timezone from offset in seconds (0 returns UTC)
if (tzoffset == 0) {
datetime_state *st = get_datetime_state();
return Py_NewRef(st->utc);
return Py_NewRef(CONST_UTC(NO_STATE));
}
PyObject *delta = new_delta(0, tzoffset, tz_useconds, 1);
@ -1920,11 +2058,13 @@ delta_to_microseconds(PyDateTime_Delta *self)
PyObject *x3 = NULL;
PyObject *result = NULL;
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
x1 = PyLong_FromLong(GET_TD_DAYS(self));
if (x1 == NULL)
goto Done;
datetime_state *st = get_datetime_state();
x2 = PyNumber_Multiply(x1, st->seconds_per_day); /* days in seconds */
x2 = PyNumber_Multiply(x1, CONST_SEC_PER_DAY(st)); /* days in seconds */
if (x2 == NULL)
goto Done;
Py_SETREF(x1, NULL);
@ -1941,7 +2081,7 @@ delta_to_microseconds(PyDateTime_Delta *self)
/* x1 = */ x2 = NULL;
/* x3 has days+seconds in seconds */
x1 = PyNumber_Multiply(x3, st->us_per_second); /* us */
x1 = PyNumber_Multiply(x3, CONST_US_PER_SECOND(st)); /* us */
if (x1 == NULL)
goto Done;
Py_SETREF(x3, NULL);
@ -1957,6 +2097,7 @@ Done:
Py_XDECREF(x1);
Py_XDECREF(x2);
Py_XDECREF(x3);
RELEASE_CURRENT_STATE(st, current_mod);
return result;
}
@ -1996,8 +2137,10 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
PyObject *num = NULL;
PyObject *result = NULL;
datetime_state *st = get_datetime_state();
tuple = checked_divmod(pyus, st->us_per_second);
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
tuple = checked_divmod(pyus, CONST_US_PER_SECOND(st));
if (tuple == NULL) {
goto Done;
}
@ -2015,7 +2158,7 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
num = Py_NewRef(PyTuple_GET_ITEM(tuple, 0)); /* leftover seconds */
Py_DECREF(tuple);
tuple = checked_divmod(num, st->seconds_per_day);
tuple = checked_divmod(num, CONST_SEC_PER_DAY(st));
if (tuple == NULL)
goto Done;
Py_DECREF(num);
@ -2040,6 +2183,7 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
Done:
Py_XDECREF(tuple);
Py_XDECREF(num);
RELEASE_CURRENT_STATE(st, current_mod);
return result;
BadDivmod:
@ -2049,7 +2193,7 @@ BadDivmod:
}
#define microseconds_to_delta(pymicros) \
microseconds_to_delta_ex(pymicros, get_datetime_state()->delta_type)
microseconds_to_delta_ex(pymicros, DELTA_TYPE(NO_STATE))
static PyObject *
multiply_int_timedelta(PyObject *intobj, PyDateTime_Delta *delta)
@ -2577,6 +2721,9 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
PyObject *self = NULL;
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
/* Argument objects. */
PyObject *day = NULL;
PyObject *second = NULL;
@ -2615,29 +2762,28 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
y = accum("microseconds", x, us, _PyLong_GetOne(), &leftover_us);
CLEANUP;
}
datetime_state *st = get_datetime_state();
if (ms) {
y = accum("milliseconds", x, ms, st->us_per_ms, &leftover_us);
y = accum("milliseconds", x, ms, CONST_US_PER_MS(st), &leftover_us);
CLEANUP;
}
if (second) {
y = accum("seconds", x, second, st->us_per_second, &leftover_us);
y = accum("seconds", x, second, CONST_US_PER_SECOND(st), &leftover_us);
CLEANUP;
}
if (minute) {
y = accum("minutes", x, minute, st->us_per_minute, &leftover_us);
y = accum("minutes", x, minute, CONST_US_PER_MINUTE(st), &leftover_us);
CLEANUP;
}
if (hour) {
y = accum("hours", x, hour, st->us_per_hour, &leftover_us);
y = accum("hours", x, hour, CONST_US_PER_HOUR(st), &leftover_us);
CLEANUP;
}
if (day) {
y = accum("days", x, day, st->us_per_day, &leftover_us);
y = accum("days", x, day, CONST_US_PER_DAY(st), &leftover_us);
CLEANUP;
}
if (week) {
y = accum("weeks", x, week, st->us_per_week, &leftover_us);
y = accum("weeks", x, week, CONST_US_PER_WEEK(st), &leftover_us);
CLEANUP;
}
if (leftover_us) {
@ -2679,7 +2825,9 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
self = microseconds_to_delta_ex(x, type);
Py_DECREF(x);
Done:
RELEASE_CURRENT_STATE(st, current_mod);
return self;
#undef CLEANUP
@ -2792,9 +2940,12 @@ delta_total_seconds(PyObject *self, PyObject *Py_UNUSED(ignored))
if (total_microseconds == NULL)
return NULL;
datetime_state *st = get_datetime_state();
total_seconds = PyNumber_TrueDivide(total_microseconds, st->us_per_second);
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
total_seconds = PyNumber_TrueDivide(total_microseconds, CONST_US_PER_SECOND(st));
RELEASE_CURRENT_STATE(st, current_mod);
Py_DECREF(total_microseconds);
return total_seconds;
}
@ -3547,9 +3698,12 @@ date_isocalendar(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored))
week = 0;
}
datetime_state *st = get_datetime_state();
PyObject *v = iso_calendar_date_new_impl(st->isocalendar_date_type,
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
PyObject *v = iso_calendar_date_new_impl(ISOCALENDAR_DATE_TYPE(st),
year, week + 1, day + 1);
RELEASE_CURRENT_STATE(st, current_mod);
if (v == NULL) {
return NULL;
}
@ -4018,9 +4172,8 @@ timezone_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
PyObject *offset;
PyObject *name = NULL;
datetime_state *st = get_datetime_state();
if (PyArg_ParseTupleAndKeywords(args, kw, "O!|U:timezone", timezone_kws,
st->delta_type, &offset, &name))
DELTA_TYPE(NO_STATE), &offset, &name))
return new_timezone(offset, name);
return NULL;
@ -4073,8 +4226,7 @@ timezone_repr(PyDateTime_TimeZone *self)
to use Py_TYPE(self)->tp_name here. */
const char *type_name = Py_TYPE(self)->tp_name;
datetime_state *st = get_datetime_state();
if (((PyObject *)self) == st->utc) {
if ((PyObject *)self == CONST_UTC(NO_STATE)) {
return PyUnicode_FromFormat("%s.utc", type_name);
}
@ -4096,8 +4248,7 @@ timezone_str(PyDateTime_TimeZone *self)
if (self->name != NULL) {
return Py_NewRef(self->name);
}
datetime_state *st = get_datetime_state();
if ((PyObject *)self == st->utc ||
if ((PyObject *)self == CONST_UTC(NO_STATE) ||
(GET_TD_DAYS(self->offset) == 0 &&
GET_TD_SECONDS(self->offset) == 0 &&
GET_TD_MICROSECONDS(self->offset) == 0))
@ -4260,7 +4411,7 @@ static PyDateTime_TimeZone *
look_up_timezone(PyObject *offset, PyObject *name)
{
if (offset == utc_timezone.offset && name == NULL) {
return &utc_timezone;
return (PyDateTime_TimeZone *)CONST_UTC(NO_STATE);
}
return NULL;
}
@ -4777,8 +4928,7 @@ time_fromisoformat(PyObject *cls, PyObject *tstr) {
}
PyObject *t;
datetime_state *st = get_datetime_state();
if ( (PyTypeObject *)cls == st->time_type) {
if ( (PyTypeObject *)cls == TIME_TYPE(NO_STATE)) {
t = new_time(hour, minute, second, microsecond, tzinfo, 0);
} else {
t = PyObject_CallFunction(cls, "iiiiO",
@ -5376,10 +5526,9 @@ datetime_combine(PyObject *cls, PyObject *args, PyObject *kw)
PyObject *tzinfo = NULL;
PyObject *result = NULL;
datetime_state *st = get_datetime_state();
if (PyArg_ParseTupleAndKeywords(args, kw, "O!O!|O:combine", keywords,
st->date_type, &date,
st->time_type, &time, &tzinfo)) {
DATE_TYPE(NO_STATE), &date,
TIME_TYPE(NO_STATE), &time, &tzinfo)) {
if (tzinfo == NULL) {
if (HASTZINFO(time))
tzinfo = ((PyDateTime_Time *)time)->tzinfo;
@ -6209,7 +6358,6 @@ local_timezone_from_timestamp(time_t timestamp)
delta = new_delta(0, local_time_tm.tm_gmtoff, 0, 1);
#else /* HAVE_STRUCT_TM_TM_ZONE */
{
datetime_state *st = get_datetime_state();
PyObject *local_time, *utc_time;
struct tm utc_time_tm;
char buf[100];
@ -6264,8 +6412,11 @@ local_timezone(PyDateTime_DateTime *utc_time)
PyObject *one_second;
PyObject *seconds;
datetime_state *st = get_datetime_state();
delta = datetime_subtract((PyObject *)utc_time, st->epoch);
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
delta = datetime_subtract((PyObject *)utc_time, CONST_EPOCH(st));
RELEASE_CURRENT_STATE(st, current_mod);
if (delta == NULL)
return NULL;
@ -6378,7 +6529,6 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
if (result == NULL)
return NULL;
datetime_state *st = get_datetime_state();
/* Make sure result is aware and UTC. */
if (!HASTZINFO(result)) {
temp = (PyObject *)result;
@ -6390,7 +6540,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
DATE_GET_MINUTE(result),
DATE_GET_SECOND(result),
DATE_GET_MICROSECOND(result),
st->utc,
CONST_UTC(NO_STATE),
DATE_GET_FOLD(result),
Py_TYPE(result));
Py_DECREF(temp);
@ -6399,7 +6549,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
}
else {
/* Result is already aware - just replace tzinfo. */
Py_SETREF(result->tzinfo, Py_NewRef(st->utc));
Py_SETREF(result->tzinfo, Py_NewRef(CONST_UTC(NO_STATE)));
}
/* Attach new tzinfo and let fromutc() do the rest. */
@ -6503,9 +6653,12 @@ datetime_timestamp(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored))
PyObject *result;
if (HASTZINFO(self) && self->tzinfo != Py_None) {
datetime_state *st = get_datetime_state();
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
PyObject *delta;
delta = datetime_subtract((PyObject *)self, st->epoch);
delta = datetime_subtract((PyObject *)self, CONST_EPOCH(st));
RELEASE_CURRENT_STATE(st, current_mod);
if (delta == NULL)
return NULL;
result = delta_total_seconds(delta, NULL);
@ -6839,23 +6992,6 @@ get_datetime_capi(void)
return &capi;
}
static int
datetime_clear(PyObject *module)
{
datetime_state *st = get_datetime_state();
Py_CLEAR(st->us_per_ms);
Py_CLEAR(st->us_per_second);
Py_CLEAR(st->us_per_minute);
Py_CLEAR(st->us_per_hour);
Py_CLEAR(st->us_per_day);
Py_CLEAR(st->us_per_week);
Py_CLEAR(st->seconds_per_day);
Py_CLEAR(st->utc);
Py_CLEAR(st->epoch);
return 0;
}
static PyObject *
create_timezone_from_delta(int days, int sec, int ms, int normalize)
{
@ -6869,25 +7005,39 @@ create_timezone_from_delta(int days, int sec, int ms, int normalize)
}
static int
init_state(datetime_state *st, PyTypeObject *PyDateTime_IsoCalendarDateType)
init_state(datetime_state *st, PyObject *module, PyObject *old_module)
{
// While datetime uses global module "state", we unly initialize it once.
// The PyLong objects created here (once per process) are not decref'd.
if (st->initialized) {
/* Each module gets its own heap types. */
#define ADD_TYPE(FIELD, SPEC, BASE) \
do { \
PyObject *cls = PyType_FromModuleAndSpec( \
module, SPEC, (PyObject *)BASE); \
if (cls == NULL) { \
return -1; \
} \
st->FIELD = (PyTypeObject *)cls; \
} while (0)
ADD_TYPE(isocalendar_date_type, &isocal_spec, &PyTuple_Type);
#undef ADD_TYPE
if (old_module != NULL) {
assert(old_module != module);
datetime_state *st_old = get_module_state(old_module);
*st = (datetime_state){
.isocalendar_date_type = st->isocalendar_date_type,
.us_per_ms = Py_NewRef(st_old->us_per_ms),
.us_per_second = Py_NewRef(st_old->us_per_second),
.us_per_minute = Py_NewRef(st_old->us_per_minute),
.us_per_hour = Py_NewRef(st_old->us_per_hour),
.us_per_day = Py_NewRef(st_old->us_per_day),
.us_per_week = Py_NewRef(st_old->us_per_week),
.seconds_per_day = Py_NewRef(st_old->seconds_per_day),
.epoch = Py_NewRef(st_old->epoch),
};
return 0;
}
/* Static types exposed by the C-API. */
st->date_type = &PyDateTime_DateType;
st->datetime_type = &PyDateTime_DateTimeType;
st->delta_type = &PyDateTime_DeltaType;
st->time_type = &PyDateTime_TimeType;
st->tzinfo_type = &PyDateTime_TZInfoType;
st->timezone_type = &PyDateTime_TimeZoneType;
/* Per-module heap types. */
st->isocalendar_date_type = PyDateTime_IsoCalendarDateType;
st->us_per_ms = PyLong_FromLong(1000);
if (st->us_per_ms == NULL) {
return -1;
@ -6921,26 +7071,54 @@ init_state(datetime_state *st, PyTypeObject *PyDateTime_IsoCalendarDateType)
return -1;
}
/* Init UTC timezone */
st->utc = create_timezone_from_delta(0, 0, 0, 0);
if (st->utc == NULL) {
return -1;
}
/* Init Unix epoch */
st->epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0, st->utc, 0);
st->epoch = new_datetime(
1970, 1, 1, 0, 0, 0, 0, (PyObject *)&utc_timezone, 0);
if (st->epoch == NULL) {
return -1;
}
st->initialized = 1;
return 0;
}
static int
traverse_state(datetime_state *st, visitproc visit, void *arg)
{
/* heap types */
Py_VISIT(st->isocalendar_date_type);
return 0;
}
static int
clear_state(datetime_state *st)
{
Py_CLEAR(st->isocalendar_date_type);
Py_CLEAR(st->us_per_ms);
Py_CLEAR(st->us_per_second);
Py_CLEAR(st->us_per_minute);
Py_CLEAR(st->us_per_hour);
Py_CLEAR(st->us_per_day);
Py_CLEAR(st->us_per_week);
Py_CLEAR(st->seconds_per_day);
Py_CLEAR(st->epoch);
return 0;
}
static int
_datetime_exec(PyObject *module)
{
int rc = -1;
datetime_state *st = get_module_state(module);
PyInterpreterState *interp = PyInterpreterState_Get();
PyObject *old_module = get_current_module(interp);
if (PyErr_Occurred()) {
assert(old_module == NULL);
goto error;
}
/* We actually set the "current" module right before a successful return. */
// `&...` is not a constant expression according to a strict reading
// of C standards. Fill tp_base at run-time rather than statically.
// See https://bugs.python.org/issue40777
@ -6953,6 +7131,7 @@ _datetime_exec(PyObject *module)
&PyDateTime_TimeType,
&PyDateTime_DeltaType,
&PyDateTime_TZInfoType,
/* Indirectly, via the utc object. */
&PyDateTime_TimeZoneType,
};
@ -6962,29 +7141,16 @@ _datetime_exec(PyObject *module)
}
}
#define CREATE_TYPE(VAR, SPEC, BASE) \
do { \
VAR = (PyTypeObject *)PyType_FromModuleAndSpec( \
module, SPEC, (PyObject *)BASE); \
if (VAR == NULL) { \
goto error; \
} \
} while (0)
PyTypeObject *PyDateTime_IsoCalendarDateType = NULL;
datetime_state *st = get_datetime_state();
if (!st->initialized) {
CREATE_TYPE(PyDateTime_IsoCalendarDateType, &isocal_spec, &PyTuple_Type);
}
#undef CREATE_TYPE
if (init_state(st, PyDateTime_IsoCalendarDateType) < 0) {
if (init_state(st, module, old_module) < 0) {
goto error;
}
/* For now we only set the objects on the static types once.
* We will relax that once each types __dict__ is per-interpreter. */
#define DATETIME_ADD_MACRO(dict, c, value_expr) \
do { \
if (PyDict_GetItemString(dict, c) == NULL) { \
assert(!PyErr_Occurred()); \
PyObject *value = (value_expr); \
if (value == NULL) { \
goto error; \
@ -6994,29 +7160,30 @@ _datetime_exec(PyObject *module)
goto error; \
} \
Py_DECREF(value); \
} \
} while(0)
/* timedelta values */
PyObject *d = st->delta_type->tp_dict;
PyObject *d = PyDateTime_DeltaType.tp_dict;
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
DATETIME_ADD_MACRO(d, "min", new_delta(-MAX_DELTA_DAYS, 0, 0, 0));
DATETIME_ADD_MACRO(d, "max",
new_delta(MAX_DELTA_DAYS, 24*3600-1, 1000000-1, 0));
/* date values */
d = st->date_type->tp_dict;
d = PyDateTime_DateType.tp_dict;
DATETIME_ADD_MACRO(d, "min", new_date(1, 1, 1));
DATETIME_ADD_MACRO(d, "max", new_date(MAXYEAR, 12, 31));
DATETIME_ADD_MACRO(d, "resolution", new_delta(1, 0, 0, 0));
/* time values */
d = st->time_type->tp_dict;
d = PyDateTime_TimeType.tp_dict;
DATETIME_ADD_MACRO(d, "min", new_time(0, 0, 0, 0, Py_None, 0));
DATETIME_ADD_MACRO(d, "max", new_time(23, 59, 59, 999999, Py_None, 0));
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
/* datetime values */
d = st->datetime_type->tp_dict;
d = PyDateTime_DateTimeType.tp_dict;
DATETIME_ADD_MACRO(d, "min",
new_datetime(1, 1, 1, 0, 0, 0, 0, Py_None, 0));
DATETIME_ADD_MACRO(d, "max", new_datetime(MAXYEAR, 12, 31, 23, 59, 59,
@ -7024,8 +7191,8 @@ _datetime_exec(PyObject *module)
DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
/* timezone values */
d = st->timezone_type->tp_dict;
if (PyDict_SetItemString(d, "utc", st->utc) < 0) {
d = PyDateTime_TimeZoneType.tp_dict;
if (PyDict_SetItemString(d, "utc", (PyObject *)&utc_timezone) < 0) {
goto error;
}
@ -7034,12 +7201,13 @@ _datetime_exec(PyObject *module)
* values. This may change in the future.*/
/* -23:59 */
PyObject *min = create_timezone_from_delta(-1, 60, 0, 1);
DATETIME_ADD_MACRO(d, "min", min);
DATETIME_ADD_MACRO(d, "min", create_timezone_from_delta(-1, 60, 0, 1));
/* +23:59 */
PyObject *max = create_timezone_from_delta(0, (23 * 60 + 59) * 60, 0, 0);
DATETIME_ADD_MACRO(d, "max", max);
DATETIME_ADD_MACRO(
d, "max", create_timezone_from_delta(0, (23 * 60 + 59) * 60, 0, 0));
#undef DATETIME_ADD_MACRO
/* Add module level attributes */
if (PyModule_AddIntMacro(module, MINYEAR) < 0) {
@ -7048,7 +7216,7 @@ _datetime_exec(PyObject *module)
if (PyModule_AddIntMacro(module, MAXYEAR) < 0) {
goto error;
}
if (PyModule_AddObjectRef(module, "UTC", st->utc) < 0) {
if (PyModule_AddObjectRef(module, "UTC", (PyObject *)&utc_timezone) < 0) {
goto error;
}
@ -7081,13 +7249,20 @@ _datetime_exec(PyObject *module)
static_assert(DI100Y == 25 * DI4Y - 1, "DI100Y");
assert(DI100Y == days_before_year(100+1));
return 0;
if (set_current_module(interp, module) < 0) {
goto error;
}
rc = 0;
goto finally;
error:
datetime_clear(module);
return -1;
clear_state(st);
finally:
Py_XDECREF(old_module);
return rc;
}
#undef DATETIME_ADD_MACRO
static PyModuleDef_Slot module_slots[] = {
{Py_mod_exec, _datetime_exec},
@ -7096,13 +7271,46 @@ static PyModuleDef_Slot module_slots[] = {
{0, NULL},
};
static int
module_traverse(PyObject *mod, visitproc visit, void *arg)
{
datetime_state *st = get_module_state(mod);
traverse_state(st, visit, arg);
return 0;
}
static int
module_clear(PyObject *mod)
{
datetime_state *st = get_module_state(mod);
clear_state(st);
PyInterpreterState *interp = PyInterpreterState_Get();
clear_current_module(interp, mod);
return 0;
}
static void
module_free(void *mod)
{
datetime_state *st = get_module_state((PyObject *)mod);
clear_state(st);
PyInterpreterState *interp = PyInterpreterState_Get();
clear_current_module(interp, (PyObject *)mod);
}
static PyModuleDef datetimemodule = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "_datetime",
.m_doc = "Fast implementation of the datetime type.",
.m_size = 0,
.m_size = sizeof(datetime_state),
.m_methods = module_methods,
.m_slots = module_slots,
.m_traverse = module_traverse,
.m_clear = module_clear,
.m_free = module_free,
};
PyMODINIT_FUNC