bpo-37205: time.time() cannot fail with fatal error (GH-23314)

time.time(), time.perf_counter() and time.monotonic() functions can
no longer fail with a Python fatal error, instead raise a regular
Python exception on failure.

Remove _PyTime_Init(): don't check system, monotonic and perf counter
clocks at startup anymore.

On error, _PyTime_GetSystemClock(), _PyTime_GetMonotonicClock() and
_PyTime_GetPerfCounter() now silently ignore the error and return 0.
They cannot fail with a Python fatal error anymore.

Add py_mach_timebase_info() and win_perf_counter_frequency()
sub-functions.
This commit is contained in:
Victor Stinner 2020-11-16 16:08:05 +01:00 committed by GitHub
parent 5909a494cd
commit ae6cd7cfda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 228 additions and 158 deletions

View File

@ -164,22 +164,6 @@ PyAPI_FUNC(_PyTime_t) _PyTime_MulDiv(_PyTime_t ticks,
_PyTime_t mul,
_PyTime_t div);
/* Get the current time from the system clock.
The function cannot fail. _PyTime_Init() ensures that the system clock
works. */
PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void);
/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards.
The clock is not affected by system clock updates. The reference point of
the returned value is undefined, so that only the difference between the
results of consecutive calls is valid.
The function cannot fail. _PyTime_Init() ensures that a monotonic clock
is available and works. */
PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void);
/* Structure used by time.get_clock_info() */
typedef struct {
const char *implementation;
@ -189,13 +173,34 @@ typedef struct {
} _Py_clock_info_t;
/* Get the current time from the system clock.
* Fill clock information if info is not NULL.
* Raise an exception and return -1 on error, return 0 on success.
If the internal clock fails, silently ignore the error and return 0.
On integer overflow, silently ignore the overflow and truncated the clock to
_PyTime_MIN or _PyTime_MAX.
Use _PyTime_GetSystemClockWithInfo() to check for failure. */
PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void);
/* Get the current time from the system clock.
* On success, set *t and *info (if not NULL), and return 0.
* On error, raise an exception and return -1.
*/
PyAPI_FUNC(int) _PyTime_GetSystemClockWithInfo(
_PyTime_t *t,
_Py_clock_info_t *info);
/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards.
The clock is not affected by system clock updates. The reference point of
the returned value is undefined, so that only the difference between the
results of consecutive calls is valid.
If the internal clock fails, silently ignore the error and return 0.
On integer overflow, silently ignore the overflow and truncated the clock to
_PyTime_MIN or _PyTime_MAX.
Use _PyTime_GetMonotonicClockWithInfo() to check for failure. */
PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void);
/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards.
The clock is not affected by system clock updates. The reference point of
the returned value is undefined, so that only the difference between the
@ -209,10 +214,6 @@ PyAPI_FUNC(int) _PyTime_GetMonotonicClockWithInfo(
_Py_clock_info_t *info);
/* Initialize time.
Return 0 on success, raise an exception and return -1 on error. */
PyAPI_FUNC(int) _PyTime_Init(void);
/* Converts a timestamp to the Gregorian time, using the local time zone.
Return 0 on success, raise an exception and return -1 on error. */
PyAPI_FUNC(int) _PyTime_localtime(time_t t, struct tm *tm);
@ -224,8 +225,11 @@ PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm);
/* Get the performance counter: clock with the highest available resolution to
measure a short duration.
The function cannot fail. _PyTime_Init() ensures that the system clock
works. */
If the internal clock fails, silently ignore the error and return 0.
On integer overflow, silently ignore the overflow and truncated the clock to
_PyTime_MIN or _PyTime_MAX.
Use _PyTime_GetPerfCounterWithInfo() to check for failure. */
PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void);
/* Get the performance counter: clock with the highest available resolution to

View File

@ -0,0 +1,3 @@
:func:`time.time()`, :func:`time.perf_counter()` and
:func:`time.monotonic()` functions can no longer fail with a Python fatal
error, instead raise a regular Python exception on failure.

View File

@ -74,10 +74,21 @@ _PyFloat_FromPyTime(_PyTime_t t)
}
static int
get_system_time(_PyTime_t *t)
{
// Avoid _PyTime_GetSystemClock() which silently ignores errors.
return _PyTime_GetSystemClockWithInfo(t, NULL);
}
static PyObject *
time_time(PyObject *self, PyObject *unused)
{
_PyTime_t t = _PyTime_GetSystemClock();
_PyTime_t t;
if (get_system_time(&t) < 0) {
return NULL;
}
return _PyFloat_FromPyTime(t);
}
@ -91,7 +102,10 @@ Fractions of a second may be present if the system clock provides them.");
static PyObject *
time_time_ns(PyObject *self, PyObject *unused)
{
_PyTime_t t = _PyTime_GetSystemClock();
_PyTime_t t;
if (get_system_time(&t) < 0) {
return NULL;
}
return _PyTime_AsNanosecondsObject(t);
}
@ -147,15 +161,6 @@ _PyTime_GetClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
}
#endif /* HAVE_CLOCK */
static PyObject*
perf_counter(_Py_clock_info_t *info)
{
_PyTime_t t;
if (_PyTime_GetPerfCounterWithInfo(&t, info) < 0) {
return NULL;
}
return _PyFloat_FromPyTime(t);
}
#ifdef HAVE_CLOCK_GETTIME
@ -1096,10 +1101,22 @@ the local timezone used by methods such as localtime, but this behaviour\n\
should not be relied on.");
#endif /* HAVE_WORKING_TZSET */
static int
get_monotonic(_PyTime_t *t)
{
// Avoid _PyTime_GetMonotonicClock() which silently ignores errors.
return _PyTime_GetMonotonicClockWithInfo(t, NULL);
}
static PyObject *
time_monotonic(PyObject *self, PyObject *unused)
{
_PyTime_t t = _PyTime_GetMonotonicClock();
_PyTime_t t;
if (get_monotonic(&t) < 0) {
return NULL;
}
return _PyFloat_FromPyTime(t);
}
@ -1111,7 +1128,10 @@ Monotonic clock, cannot go backward.");
static PyObject *
time_monotonic_ns(PyObject *self, PyObject *unused)
{
_PyTime_t t = _PyTime_GetMonotonicClock();
_PyTime_t t;
if (get_monotonic(&t) < 0) {
return NULL;
}
return _PyTime_AsNanosecondsObject(t);
}
@ -1120,10 +1140,23 @@ PyDoc_STRVAR(monotonic_ns_doc,
\n\
Monotonic clock, cannot go backward, as nanoseconds.");
static int
get_perf_counter(_PyTime_t *t)
{
// Avoid _PyTime_GetPerfCounter() which silently ignores errors.
return _PyTime_GetPerfCounterWithInfo(t, NULL);
}
static PyObject *
time_perf_counter(PyObject *self, PyObject *unused)
{
return perf_counter(NULL);
_PyTime_t t;
if (get_perf_counter(&t) < 0) {
return NULL;
}
return _PyFloat_FromPyTime(t);
}
PyDoc_STRVAR(perf_counter_doc,
@ -1131,10 +1164,14 @@ PyDoc_STRVAR(perf_counter_doc,
\n\
Performance counter for benchmarking.");
static PyObject *
time_perf_counter_ns(PyObject *self, PyObject *unused)
{
_PyTime_t t = _PyTime_GetPerfCounter();
_PyTime_t t;
if (get_perf_counter(&t) < 0) {
return NULL;
}
return _PyTime_AsNanosecondsObject(t);
}
@ -2025,7 +2062,10 @@ pysleep(_PyTime_t secs)
HANDLE hInterruptEvent;
#endif
deadline = _PyTime_GetMonotonicClock() + secs;
if (get_monotonic(&monotonic) < 0) {
return -1;
}
deadline = monotonic + secs;
do {
#ifndef MS_WINDOWS
@ -2077,10 +2117,13 @@ pysleep(_PyTime_t secs)
if (PyErr_CheckSignals())
return -1;
monotonic = _PyTime_GetMonotonicClock();
if (get_monotonic(&monotonic) < 0) {
return -1;
}
secs = deadline - monotonic;
if (secs < 0)
if (secs < 0) {
break;
}
/* retry with the recomputed delay */
} while (1);

View File

@ -763,12 +763,6 @@ pycore_interp_init(PyThreadState *tstate)
goto done;
}
if (_Py_IsMainInterpreter(tstate)) {
if (_PyTime_Init() < 0) {
return _PyStatus_ERR("can't initialize time");
}
}
status = _PySys_Create(tstate, &sysmod);
if (_PyStatus_EXCEPTION(status)) {
goto done;

View File

@ -304,8 +304,8 @@ pytime_fromtimespec(_PyTime_t *tp, struct timespec *ts, int raise)
if (_PyTime_check_mul_overflow(t, SEC_TO_NS)) {
if (raise) {
_PyTime_overflow();
}
res = -1;
}
t = (t > 0) ? _PyTime_MAX : _PyTime_MIN;
}
else {
@ -318,8 +318,8 @@ pytime_fromtimespec(_PyTime_t *tp, struct timespec *ts, int raise)
if (t > _PyTime_MAX - nsec) {
if (raise) {
_PyTime_overflow();
}
res = -1;
}
t = _PyTime_MAX;
}
else {
@ -350,8 +350,8 @@ pytime_fromtimeval(_PyTime_t *tp, struct timeval *tv, int raise)
if (_PyTime_check_mul_overflow(t, SEC_TO_NS)) {
if (raise) {
_PyTime_overflow();
}
res = -1;
}
t = (t > 0) ? _PyTime_MAX : _PyTime_MIN;
}
else {
@ -364,8 +364,8 @@ pytime_fromtimeval(_PyTime_t *tp, struct timeval *tv, int raise)
if (t > _PyTime_MAX - usec) {
if (raise) {
_PyTime_overflow();
}
res = -1;
}
t = _PyTime_MAX;
}
else {
@ -656,7 +656,7 @@ _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts)
#endif
static int
pygettimeofday(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
{
#ifdef MS_WINDOWS
FILETIME system_time;
@ -769,9 +769,10 @@ _PyTime_t
_PyTime_GetSystemClock(void)
{
_PyTime_t t;
if (pygettimeofday(&t, NULL, 0) < 0) {
/* should not happen, _PyTime_Init() checked the clock at startup */
Py_FatalError("pygettimeofday() failed");
if (py_get_system_clock(&t, NULL, 0) < 0) {
// If clock_gettime(CLOCK_REALTIME) or gettimeofday() fails:
// silently ignore the failure and return 0.
t = 0;
}
return t;
}
@ -779,11 +780,61 @@ _PyTime_GetSystemClock(void)
int
_PyTime_GetSystemClockWithInfo(_PyTime_t *t, _Py_clock_info_t *info)
{
return pygettimeofday(t, info, 1);
return py_get_system_clock(t, info, 1);
}
#if __APPLE__
static int
pymonotonic(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
py_mach_timebase_info(_PyTime_t *pnumer, _PyTime_t *pdenom, int raise)
{
static mach_timebase_info_data_t timebase;
/* According to the Technical Q&A QA1398, mach_timebase_info() cannot
fail: https://developer.apple.com/library/mac/#qa/qa1398/ */
(void)mach_timebase_info(&timebase);
/* Sanity check: should never occur in practice */
if (timebase.numer < 1 || timebase.denom < 1) {
if (raise) {
PyErr_SetString(PyExc_RuntimeError,
"invalid mach_timebase_info");
}
return -1;
}
/* Check that timebase.numer and timebase.denom can be casted to
_PyTime_t. In practice, timebase uses uint32_t, so casting cannot
overflow. At the end, only make sure that the type is uint32_t
(_PyTime_t is 64-bit long). */
Py_BUILD_ASSERT(sizeof(timebase.numer) < sizeof(_PyTime_t));
Py_BUILD_ASSERT(sizeof(timebase.denom) < sizeof(_PyTime_t));
/* Make sure that (ticks * timebase.numer) cannot overflow in
_PyTime_MulDiv(), with ticks < timebase.denom.
Known time bases:
* always (1, 1) on Intel
* (1000000000, 33333335) or (1000000000, 25000000) on PowerPC
None of these time bases can overflow with 64-bit _PyTime_t, but
check for overflow, just in case. */
if ((_PyTime_t)timebase.numer > _PyTime_MAX / (_PyTime_t)timebase.denom) {
if (raise) {
PyErr_SetString(PyExc_OverflowError,
"mach_timebase_info is too large");
}
return -1;
}
*pnumer = (_PyTime_t)timebase.numer;
*pdenom = (_PyTime_t)timebase.denom;
return 0;
}
#endif
static int
py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
{
#if defined(MS_WINDOWS)
ULONGLONG ticks;
@ -800,10 +851,12 @@ pymonotonic(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
_PyTime_overflow();
return -1;
}
/* Hello, time traveler! */
Py_FatalError("pymonotonic: integer overflow");
// Truncate to _PyTime_MAX silently.
*tp = _PyTime_MAX;
}
else {
*tp = t * MS_TO_NS;
}
if (info) {
DWORD timeAdjustment, timeIncrement;
@ -821,56 +874,23 @@ pymonotonic(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
}
#elif defined(__APPLE__)
static mach_timebase_info_data_t timebase;
uint64_t ticks;
if (timebase.denom == 0) {
/* According to the Technical Q&A QA1398, mach_timebase_info() cannot
fail: https://developer.apple.com/library/mac/#qa/qa1398/ */
(void)mach_timebase_info(&timebase);
/* Sanity check: should never occur in practice */
if (timebase.numer < 1 || timebase.denom < 1) {
PyErr_SetString(PyExc_RuntimeError,
"invalid mach_timebase_info");
return -1;
}
/* Check that timebase.numer and timebase.denom can be casted to
_PyTime_t. In practice, timebase uses uint32_t, so casting cannot
overflow. At the end, only make sure that the type is uint32_t
(_PyTime_t is 64-bit long). */
assert(sizeof(timebase.numer) < sizeof(_PyTime_t));
assert(sizeof(timebase.denom) < sizeof(_PyTime_t));
/* Make sure that (ticks * timebase.numer) cannot overflow in
_PyTime_MulDiv(), with ticks < timebase.denom.
Known time bases:
* always (1, 1) on Intel
* (1000000000, 33333335) or (1000000000, 25000000) on PowerPC
None of these time bases can overflow with 64-bit _PyTime_t, but
check for overflow, just in case. */
if ((_PyTime_t)timebase.numer > _PyTime_MAX / (_PyTime_t)timebase.denom) {
PyErr_SetString(PyExc_OverflowError,
"mach_timebase_info is too large");
static _PyTime_t timebase_numer = 0;
static _PyTime_t timebase_denom = 0;
if (timebase_denom == 0) {
if (py_mach_timebase_info(&timebase_numer, &timebase_denom, raise) < 0) {
return -1;
}
}
if (info) {
info->implementation = "mach_absolute_time()";
info->resolution = (double)timebase.numer / (double)timebase.denom * 1e-9;
info->resolution = (double)timebase_numer / (double)timebase_denom * 1e-9;
info->monotonic = 1;
info->adjustable = 0;
}
ticks = mach_absolute_time();
*tp = _PyTime_MulDiv(ticks,
(_PyTime_t)timebase.numer,
(_PyTime_t)timebase.denom);
uint64_t ticks = mach_absolute_time();
*tp = _PyTime_MulDiv((_PyTime_t)ticks, timebase_numer, timebase_denom);
#elif defined(__hpux)
hrtime_t time;
@ -934,10 +954,10 @@ _PyTime_t
_PyTime_GetMonotonicClock(void)
{
_PyTime_t t;
if (pymonotonic(&t, NULL, 0) < 0) {
/* should not happen, _PyTime_Init() checked that monotonic clock at
startup */
Py_FatalError("pymonotonic() failed");
if (py_get_monotonic_clock(&t, NULL, 0) < 0) {
// If mach_timebase_info(), clock_gettime() or gethrtime() fails:
// silently ignore the failure and return 0.
t = 0;
}
return t;
}
@ -945,31 +965,31 @@ _PyTime_GetMonotonicClock(void)
int
_PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
{
return pymonotonic(tp, info, 1);
return py_get_monotonic_clock(tp, info, 1);
}
#ifdef MS_WINDOWS
static int
win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info)
win_perf_counter_frequency(LONGLONG *pfrequency, int raise)
{
static LONGLONG frequency = 0;
LARGE_INTEGER now;
LONGLONG ticksll;
_PyTime_t ticks;
LONGLONG frequency;
if (frequency == 0) {
LARGE_INTEGER freq;
if (!QueryPerformanceFrequency(&freq)) {
if (raise) {
PyErr_SetFromWindowsErr(0);
}
return -1;
}
frequency = freq.QuadPart;
/* Sanity check: should never occur in practice */
if (frequency < 1) {
if (raise) {
PyErr_SetString(PyExc_RuntimeError,
"invalid QueryPerformanceFrequency");
}
return -1;
}
@ -986,13 +1006,28 @@ win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info)
None of these frequencies can overflow with 64-bit _PyTime_t, but
check for overflow, just in case. */
if (frequency > _PyTime_MAX
|| frequency > (LONGLONG)_PyTime_MAX / (LONGLONG)SEC_TO_NS) {
|| frequency > (LONGLONG)_PyTime_MAX / (LONGLONG)SEC_TO_NS)
{
if (raise) {
PyErr_SetString(PyExc_OverflowError,
"QueryPerformanceFrequency is too large");
}
return -1;
}
QueryPerformanceCounter(&now);
*pfrequency = frequency;
return 0;
}
static int
py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
{
static LONGLONG frequency = 0;
if (frequency == 0) {
if (win_perf_counter_frequency(&frequency, raise) < 0) {
return -1;
}
}
if (info) {
@ -1002,11 +1037,13 @@ win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info)
info->adjustable = 0;
}
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
ticksll = now.QuadPart;
LONGLONG ticksll = now.QuadPart;
/* Make sure that casting LONGLONG to _PyTime_t cannot overflow,
both types are signed */
_PyTime_t ticks;
Py_BUILD_ASSERT(sizeof(ticksll) <= sizeof(ticks));
ticks = (_PyTime_t)ticksll;
@ -1020,7 +1057,7 @@ int
_PyTime_GetPerfCounterWithInfo(_PyTime_t *t, _Py_clock_info_t *info)
{
#ifdef MS_WINDOWS
return win_perf_counter(t, info);
return py_get_win_perf_counter(t, info, 1);
#else
return _PyTime_GetMonotonicClockWithInfo(t, info);
#endif
@ -1031,32 +1068,21 @@ _PyTime_t
_PyTime_GetPerfCounter(void)
{
_PyTime_t t;
if (_PyTime_GetPerfCounterWithInfo(&t, NULL)) {
Py_FatalError("_PyTime_GetPerfCounterWithInfo() failed");
int res;
#ifdef MS_WINDOWS
res = py_get_win_perf_counter(&t, NULL, 0);
#else
res = py_get_monotonic_clock(&t, NULL, 0);
#endif
if (res < 0) {
// If win_perf_counter_frequency() or py_get_monotonic_clock() fails:
// silently ignore the failure and return 0.
t = 0;
}
return t;
}
int
_PyTime_Init(void)
{
/* check that time.time(), time.monotonic() and time.perf_counter() clocks
are working properly to not have to check for exceptions at runtime. If
a clock works once, it cannot fail in next calls. */
_PyTime_t t;
if (_PyTime_GetSystemClockWithInfo(&t, NULL) < 0) {
return -1;
}
if (_PyTime_GetMonotonicClockWithInfo(&t, NULL) < 0) {
return -1;
}
if (_PyTime_GetPerfCounterWithInfo(&t, NULL) < 0) {
return -1;
}
return 0;
}
int
_PyTime_localtime(time_t t, struct tm *tm)
{