Skip to content

Commit

Permalink
bpo-37205: time.time() cannot fail with fatal error (GH-23314)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vstinner committed Nov 16, 2020
1 parent 5909a49 commit ae6cd7c
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 158 deletions.
52 changes: 28 additions & 24 deletions Include/pytime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
87 changes: 65 additions & 22 deletions Modules/timemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
#define _Py_tzname tzname
#endif

#if defined(__APPLE__ ) && defined(__has_builtin)
#if defined(__APPLE__ ) && defined(__has_builtin)
# if __has_builtin(__builtin_available)
# define HAVE_CLOCK_GETTIME_RUNTIME __builtin_available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
# endif
Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -147,20 +161,11 @@ _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

#ifdef __APPLE__
/*
/*
* The clock_* functions will be removed from the module
* dict entirely when the C API is not available.
*/
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -1120,21 +1140,38 @@ 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,
"perf_counter() -> float\n\
\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);
}

Expand Down Expand Up @@ -1421,7 +1458,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)

#if defined(__APPLE__) && defined(__has_attribute) && __has_attribute(availability)
static int
_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
__attribute__((availability(macos, introduced=10.12)))
__attribute__((availability(ios, introduced=10.0)))
__attribute__((availability(tvos, introduced=10.0)))
Expand Down Expand Up @@ -1460,7 +1497,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)

#ifdef HAVE_THREAD_TIME
#ifdef __APPLE__
/*
/*
* The clock_* functions will be removed from the module
* dict entirely when the C API is not available.
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down
6 changes: 0 additions & 6 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit ae6cd7c

Please sign in to comment.