Skip to content

gh-110850: Add PyTime_TimeUnchecked() function #115973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Doc/c-api/time.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,40 @@ with the :term:`GIL` held.
Read the monotonic clock.
See :func:`time.monotonic` for important details on this clock.

.. c:function:: PyTime_t PyTime_MonotonicUnchecked()

Similar to :c:func:`PyTime_Monotonic()`, but return ``0`` and silently
ignore the error if reading the clock fails.

The caller doesn't have to hold the GIL; the function does not raise an
exception on error.

.. c:function:: int PyTime_PerfCounter(PyTime_t *result)

Read the performance counter.
See :func:`time.perf_counter` for important details on this clock.

.. c:function:: PyTime_t PyTime_PerfCounterUnchecked()

Similar to :c:func:`PyTime_PerfCounter()`, but return ``0`` and silently
ignore the error if reading the clock fails.

The caller doesn't have to hold the GIL; the function does not raise an
exception on error.

.. c:function:: int PyTime_Time(PyTime_t *result)

Read the “wall clock” time.
See :func:`time.time` for details important on this clock.

.. c:function:: PyTime_t PyTime_TimeUnchecked()

Similar to :c:func:`PyTime_Time()`, but return ``0`` and silently ignore the
error if reading the clock fails.

The caller doesn't have to hold the GIL; the function does not raise an
exception on error.


Conversion functions
--------------------
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1571,6 +1571,9 @@ New Features
* :c:func:`PyTime_AsSecondsDouble`
:c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and
:c:func:`PyTime_Time` functions.
* :c:func:`PyTime_MonotonicUnchecked`, :c:func:`PyTime_PerfCounterUnchecked`
and :c:func:`PyTime_TimeUnchecked`: similar to previous functions, but
ignores errors and doesn't require to hold the GIL.

(Contributed by Victor Stinner and Petr Viktorin in :gh:`110850`.)

Expand Down
3 changes: 3 additions & 0 deletions Include/cpython/pytime.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t);
PyAPI_FUNC(int) PyTime_Monotonic(PyTime_t *result);
PyAPI_FUNC(int) PyTime_PerfCounter(PyTime_t *result);
PyAPI_FUNC(int) PyTime_Time(PyTime_t *result);
PyAPI_FUNC(PyTime_t) PyTime_MonotonicUnchecked(void);
PyAPI_FUNC(PyTime_t) PyTime_PerfCounterUnchecked(void);
PyAPI_FUNC(PyTime_t) PyTime_TimeUnchecked(void);

#ifdef __cplusplus
}
Expand Down
29 changes: 6 additions & 23 deletions Include/internal/pycore_time.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,10 @@ typedef struct {
double resolution;
} _Py_clock_info_t;

// Similar to PyTime_Time() but silently ignore the error and return 0 if the
// internal clock fails.
//
// Use _PyTime_TimeWithInfo() or the public PyTime_Time() to check
// for failure.
// Export for '_random' shared extension.
PyAPI_FUNC(PyTime_t) _PyTime_TimeUnchecked(void);
// Backward compatibility
#define _PyTime_TimeUnchecked PyTime_TimeUnchecked
#define _PyTime_MonotonicUnchecked PyTime_MonotonicUnchecked
#define _PyTime_PerfCounterUnchecked PyTime_PerfCounterUnchecked

// Get the current time from the system clock.
// On success, set *t and *info (if not NULL), and return 0.
Expand All @@ -262,14 +259,6 @@ extern int _PyTime_TimeWithInfo(
PyTime_t *t,
_Py_clock_info_t *info);

// Similar to PyTime_Monotonic() but silently ignore the error and return 0 if
// the internal clock fails.
//
// Use _PyTime_MonotonicWithInfo() or the public PyTime_Monotonic()
// to check for failure.
// Export for '_random' shared extension.
PyAPI_FUNC(PyTime_t) _PyTime_MonotonicUnchecked(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 @@ -294,14 +283,6 @@ PyAPI_FUNC(int) _PyTime_localtime(time_t t, struct tm *tm);
// Export for '_datetime' shared extension.
PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm);

// Similar to PyTime_PerfCounter() but silently ignore the error and return 0
// if the internal clock fails.
//
// Use _PyTime_PerfCounterWithInfo() or the public PyTime_PerfCounter() to
// check for failure.
// Export for '_lsprof' shared extension.
PyAPI_FUNC(PyTime_t) _PyTime_PerfCounterUnchecked(void);


// Get the performance counter: clock with the highest available resolution to
// measure a short duration.
Expand Down Expand Up @@ -353,6 +334,8 @@ extern double _PyTimeFraction_Resolution(
const _PyTimeFraction *frac);


extern PyStatus _PyTime_Init(void);

#ifdef __cplusplus
}
#endif
Expand Down
11 changes: 7 additions & 4 deletions Lib/test/test_capi/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
PyTime_MAX = _testcapi.PyTime_MAX
SEC_TO_NS = 10 ** 9
DAY_TO_SEC = (24 * 60 * 60)
# Worst clock resolution: maximum delta between two clock reads.
# Worst clock resolution in seconds: maximum delta between two clock reads.
CLOCK_RES = 0.050


Expand Down Expand Up @@ -59,13 +59,16 @@ def ns_to_sec(ns):
ns_to_sec(ns))

def test_monotonic(self):
# Test PyTime_Monotonic()
# Test PyTime_Monotonic() and PyTime_MonotonicUnchecked()
self.check_clock(_testcapi.PyTime_Monotonic, time.monotonic)
self.check_clock(_testcapi.PyTime_MonotonicUnchecked, time.monotonic)

def test_perf_counter(self):
# Test PyTime_PerfCounter()
# Test PyTime_PerfCounter() and PyTime_PerfCounterUnchecked()
self.check_clock(_testcapi.PyTime_PerfCounter, time.perf_counter)
self.check_clock(_testcapi.PyTime_PerfCounterUnchecked, time.perf_counter)

def test_time(self):
# Test PyTime_time()
# Test PyTime_Time() and PyTime_TimeUnchecked()
self.check_clock(_testcapi.PyTime_Time, time.time)
self.check_clock(_testcapi.PyTime_TimeUnchecked, time.time)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Add functions:

* :c:func:`PyTime_MonotonicUnchecked`
* :c:func:`PyTime_PerfCounterUnchecked`
* :c:func:`PyTime_TimeUnchecked`

Patch by Victor Stinner.
27 changes: 27 additions & 0 deletions Modules/_testcapi/time.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
}


static PyObject*
test_pytime_monotonic_unchecked(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyTime_t t = PyTime_MonotonicUnchecked();
return pytime_as_float(t);
}


static PyObject*
test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
Expand All @@ -71,6 +79,14 @@ test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
}


static PyObject*
test_pytime_perf_counter_unchecked(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyTime_t t = PyTime_PerfCounterUnchecked();
return pytime_as_float(t);
}


static PyObject*
test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
Expand All @@ -84,11 +100,22 @@ test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
}


static PyObject*
test_pytime_time_unchecked(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyTime_t t = PyTime_TimeUnchecked();
return pytime_as_float(t);
}


static PyMethodDef test_methods[] = {
{"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
{"PyTime_Monotonic", test_pytime_monotonic, METH_NOARGS},
{"PyTime_MonotonicUnchecked", test_pytime_monotonic_unchecked, METH_NOARGS},
{"PyTime_PerfCounter", test_pytime_perf_counter, METH_NOARGS},
{"PyTime_PerfCounterUnchecked", test_pytime_perf_counter_unchecked, METH_NOARGS},
{"PyTime_Time", test_pytime_time, METH_NOARGS},
{"PyTime_TimeUnchecked", test_pytime_time_unchecked, METH_NOARGS},
{NULL},
};

Expand Down
6 changes: 6 additions & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "pycore_setobject.h" // _PySet_NextEntry()
#include "pycore_sliceobject.h" // _PySlice_Fini()
#include "pycore_sysmodule.h" // _PySys_ClearAuditHooks()
#include "pycore_time.h" // _PyTime_Init()
#include "pycore_traceback.h" // _Py_DumpTracebackThreads()
#include "pycore_typeobject.h" // _PyTypes_InitTypes()
#include "pycore_typevarobject.h" // _Py_clear_generic_types()
Expand Down Expand Up @@ -525,6 +526,11 @@ pycore_init_runtime(_PyRuntimeState *runtime,

_Py_InitVersion();

status = _PyTime_Init();
if (_PyStatus_EXCEPTION(status)) {
return status;
}

status = _Py_HashRandomization_Init(config);
if (_PyStatus_EXCEPTION(status)) {
return status;
Expand Down
55 changes: 37 additions & 18 deletions Python/pytime.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "Python.h"
#include "pycore_initconfig.h" // _PyStatus_ERR()
#include "pycore_time.h" // PyTime_t

#include <time.h> // gmtime_r()
Expand Down Expand Up @@ -1289,28 +1290,33 @@ py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
#endif // MS_WINDOWS


int
_PyTime_PerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info)
static int
py_perf_counter(PyTime_t *result, _Py_clock_info_t *info, int raise_exc)
{
#ifdef MS_WINDOWS
return py_get_win_perf_counter(t, info, 1);
# define INIT_CHECK_PERF_COUNTER 1
return py_get_win_perf_counter(result, info, raise_exc);
#else
return _PyTime_MonotonicWithInfo(t, info);
// On non-Windows, _PyTime_Init() doesn't have to check py_perf_counter()
// since py_get_monotonic_clock() is already checked.
# define INIT_CHECK_PERF_COUNTER 0
return py_get_monotonic_clock(result, info, raise_exc);
#endif
}


int
_PyTime_PerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info)
{
return py_perf_counter(t, info, 1);
}


PyTime_t
_PyTime_PerfCounterUnchecked(void)
{
PyTime_t t;
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 (py_perf_counter(&t, NULL, 0) < 0) {
// If py_win_perf_counter_frequency() or py_get_monotonic_clock()
// fails: silently ignore the failure and return 0.
t = 0;
Expand All @@ -1322,13 +1328,7 @@ _PyTime_PerfCounterUnchecked(void)
int
PyTime_PerfCounter(PyTime_t *result)
{
int res;
#ifdef MS_WINDOWS
res = py_get_win_perf_counter(result, NULL, 1);
#else
res = py_get_monotonic_clock(result, NULL, 1);
#endif
if (res < 0) {
if (py_perf_counter(result, NULL, 1) < 0) {
// If py_win_perf_counter_frequency() or py_get_monotonic_clock()
// fails: silently ignore the failure and return 0.
*result = 0;
Expand Down Expand Up @@ -1419,3 +1419,22 @@ _PyDeadline_Get(PyTime_t deadline)
PyTime_t now = _PyTime_MonotonicUnchecked();
return deadline - now;
}


PyStatus
_PyTime_Init(void)
{
PyTime_t t;
if (py_get_system_clock(&t, NULL, 0) < 0) {
return _PyStatus_ERR("cannot read system clock");
}
if (py_get_monotonic_clock(&t, NULL, 0) < 0) {
return _PyStatus_ERR("cannot read monotonic clock");
}
#if INIT_CHECK_PERF_COUNTER
if (py_perf_counter(&t, NULL, 0) < 0) {
return _PyStatus_ERR("cannot read monotonic clock");
}
#endif
return _PyStatus_OK();
}