Skip to content
Merged
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
29 changes: 29 additions & 0 deletions Doc/c-api/time.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,35 @@ with the :term:`GIL` held.
See :func:`time.time` for details important on this clock.


Raw Clock Functions
-------------------

Similar to clock functions, but don't set an exception on error and don't
require the caller to hold the GIL.

On success, the functions return ``0``.

On failure, they set ``*result`` to ``0`` and return ``-1``, *without* setting
an exception. To get the cause of the error, acquire the GIL and call the
regular (non-``Raw``) function. Note that the regular function may succeed after
the ``Raw`` one failed.

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

Similar to :c:func:`PyTime_Monotonic`,
but don't set an exception on error and don't require holding the GIL.

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

Similar to :c:func:`PyTime_PerfCounter`,
but don't set an exception on error and don't require holding the GIL.

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

Similar to :c:func:`PyTime_Time`,
but don't set an exception on error and don't require holding the GIL.


Conversion functions
--------------------

Expand Down
12 changes: 9 additions & 3 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1882,9 +1882,15 @@ New Features

* :c:type:`PyTime_t` type.
* :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants.
* :c:func:`PyTime_AsSecondsDouble`
:c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and
:c:func:`PyTime_Time` functions.
* Add functions:

* :c:func:`PyTime_AsSecondsDouble`.
* :c:func:`PyTime_Monotonic`.
* :c:func:`PyTime_MonotonicRaw`.
* :c:func:`PyTime_PerfCounter`.
* :c:func:`PyTime_PerfCounterRaw`.
* :c:func:`PyTime_Time`.
* :c:func:`PyTime_TimeRaw`.

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

Expand Down
4 changes: 4 additions & 0 deletions Include/cpython/pytime.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ 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(int) PyTime_MonotonicRaw(PyTime_t *result);
PyAPI_FUNC(int) PyTime_PerfCounterRaw(PyTime_t *result);
PyAPI_FUNC(int) PyTime_TimeRaw(PyTime_t *result);

#ifdef __cplusplus
}
#endif
Expand Down
19 changes: 11 additions & 8 deletions Lib/test/test_capi/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ def test_min_max(self):
self.assertEqual(PyTime_MIN, -2**63)
self.assertEqual(PyTime_MAX, 2**63 - 1)

def check_clock(self, c_func, py_func):
t1 = c_func()
t2 = py_func()
self.assertAlmostEqual(t1, t2, delta=CLOCK_RES)

def test_assecondsdouble(self):
# Test PyTime_AsSecondsDouble()
def ns_to_sec(ns):
Expand Down Expand Up @@ -58,14 +53,22 @@ def ns_to_sec(ns):
self.assertEqual(_testcapi.PyTime_AsSecondsDouble(ns),
ns_to_sec(ns))

def check_clock(self, c_func, py_func):
t1 = c_func()
t2 = py_func()
self.assertAlmostEqual(t1, t2, delta=CLOCK_RES)

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

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

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

* :c:func:`PyTime_MonotonicRaw`
* :c:func:`PyTime_PerfCounterRaw`
* :c:func:`PyTime_TimeRaw`

Patch by Victor Stinner.
60 changes: 60 additions & 0 deletions Modules/_testcapi/time.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
PyTime_t t;
int res = PyTime_Monotonic(&t);
if (res < 0) {
assert(t == 0);
return NULL;
}
assert(res == 0);
return pytime_as_float(t);
}


static PyObject*
test_pytime_monotonic_raw(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyTime_t t;
int res;
Py_BEGIN_ALLOW_THREADS
res = PyTime_MonotonicRaw(&t);
Py_END_ALLOW_THREADS
if (res < 0) {
assert(t == 0);
PyErr_SetString(PyExc_RuntimeError, "PyTime_MonotonicRaw() failed");
return NULL;
}
assert(res == 0);
Expand All @@ -64,6 +83,25 @@ test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
PyTime_t t;
int res = PyTime_PerfCounter(&t);
if (res < 0) {
assert(t == 0);
return NULL;
}
assert(res == 0);
return pytime_as_float(t);
}


static PyObject*
test_pytime_perf_counter_raw(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyTime_t t;
int res;
Py_BEGIN_ALLOW_THREADS
res = PyTime_PerfCounterRaw(&t);
Py_END_ALLOW_THREADS
if (res < 0) {
assert(t == 0);
PyErr_SetString(PyExc_RuntimeError, "PyTime_PerfCounterRaw() failed");
return NULL;
}
assert(res == 0);
Expand All @@ -77,6 +115,25 @@ test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
PyTime_t t;
int res = PyTime_Time(&t);
if (res < 0) {
assert(t == 0);
return NULL;
}
assert(res == 0);
return pytime_as_float(t);
}


static PyObject*
test_pytime_time_raw(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyTime_t t;
int res;
Py_BEGIN_ALLOW_THREADS
res = PyTime_TimeRaw(&t);
Py_END_ALLOW_THREADS
if (res < 0) {
assert(t == 0);
PyErr_SetString(PyExc_RuntimeError, "PyTime_TimeRaw() failed");
return NULL;
}
assert(res == 0);
Expand All @@ -87,8 +144,11 @@ test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
static PyMethodDef test_methods[] = {
{"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
{"PyTime_Monotonic", test_pytime_monotonic, METH_NOARGS},
{"PyTime_MonotonicRaw", test_pytime_monotonic_raw, METH_NOARGS},
{"PyTime_PerfCounter", test_pytime_perf_counter, METH_NOARGS},
{"PyTime_PerfCounterRaw", test_pytime_perf_counter_raw, METH_NOARGS},
{"PyTime_Time", test_pytime_time, METH_NOARGS},
{"PyTime_TimeRaw", test_pytime_time_raw, METH_NOARGS},
{NULL},
};

Expand Down
93 changes: 69 additions & 24 deletions Python/pytime.c
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,10 @@ static int
py_get_system_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
{
assert(info == NULL || raise_exc);
if (raise_exc) {
// raise_exc requires to hold the GIL
assert(PyGILState_Check());
}

#ifdef MS_WINDOWS
FILETIME system_time;
Expand Down Expand Up @@ -1004,29 +1008,44 @@ py_get_system_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
}


PyTime_t
_PyTime_TimeUnchecked(void)
int
PyTime_Time(PyTime_t *result)
{
PyTime_t t;
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;
if (py_get_system_clock(result, NULL, 1) < 0) {
*result = 0;
return -1;
}
return t;
return 0;
}


int
PyTime_Time(PyTime_t *result)
PyTime_TimeRaw(PyTime_t *result)
{
if (py_get_system_clock(result, NULL, 1) < 0) {
if (py_get_system_clock(result, NULL, 0) < 0) {
*result = 0;
return -1;
}
return 0;
}


PyTime_t
_PyTime_TimeUnchecked(void)
{
PyTime_t t;
#ifdef Py_DEBUG
int result = PyTime_TimeRaw(&t);
if (result != 0) {
Py_FatalError("unable to read the system clock");
}
#else
(void)PyTime_TimeRaw(&t);
#endif
return t;
}


int
_PyTime_TimeWithInfo(PyTime_t *t, _Py_clock_info_t *info)
{
Expand Down Expand Up @@ -1140,6 +1159,10 @@ static int
py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
{
assert(info == NULL || raise_exc);
if (raise_exc) {
// raise_exc requires to hold the GIL
assert(PyGILState_Check());
}

#if defined(MS_WINDOWS)
if (py_get_win_perf_counter(tp, info, raise_exc) < 0) {
Expand Down Expand Up @@ -1225,29 +1248,44 @@ py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
}


PyTime_t
_PyTime_MonotonicUnchecked(void)
int
PyTime_Monotonic(PyTime_t *result)
{
PyTime_t t;
if (py_get_monotonic_clock(&t, NULL, 0) < 0) {
// Ignore silently the error and return 0.
t = 0;
if (py_get_monotonic_clock(result, NULL, 1) < 0) {
*result = 0;
return -1;
}
return t;
return 0;
}


int
PyTime_Monotonic(PyTime_t *result)
PyTime_MonotonicRaw(PyTime_t *result)
{
if (py_get_monotonic_clock(result, NULL, 1) < 0) {
if (py_get_monotonic_clock(result, NULL, 0) < 0) {
*result = 0;
return -1;
}
return 0;
}


PyTime_t
_PyTime_MonotonicUnchecked(void)
{
PyTime_t t;
#ifdef Py_DEBUG
int result = PyTime_MonotonicRaw(&t);
if (result != 0) {
Py_FatalError("unable to read the monotonic clock");
}
#else
(void)PyTime_MonotonicRaw(&t);
#endif
return t;
}


int
_PyTime_MonotonicWithInfo(PyTime_t *tp, _Py_clock_info_t *info)
{
Expand All @@ -1262,17 +1300,24 @@ _PyTime_PerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info)
}


PyTime_t
_PyTime_PerfCounterUnchecked(void)
int
PyTime_PerfCounter(PyTime_t *result)
{
return _PyTime_MonotonicUnchecked();
return PyTime_Monotonic(result);
}


int
PyTime_PerfCounter(PyTime_t *result)
PyTime_PerfCounterRaw(PyTime_t *result)
{
return PyTime_Monotonic(result);
return PyTime_MonotonicRaw(result);
}


PyTime_t
_PyTime_PerfCounterUnchecked(void)
{
return _PyTime_MonotonicUnchecked();
}


Expand Down