Skip to content

Commit

Permalink
pythongh-110850: Add PyTime_TimeRaw() function
Browse files Browse the repository at this point in the history
Add "Raw" variant of PyTime functions:

* PyTime_MonotonicRaw()
* PyTime_PerfCounterRaw()
* PyTime_TimeRaw()

Changes:

* Add documentation and tests. Tests release the GIL while calling
  raw clock functions.
* py_get_system_clock() and py_get_monotonic_clock() now check that
  the GIL is hold by the caller if raise_exc is non-zero.
* Reimplement "Unchecked" functions with raw clock functions.
  • Loading branch information
vstinner committed Apr 29, 2024
1 parent 23d0371 commit 4ef776b
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 32 deletions.
25 changes: 25 additions & 0 deletions Doc/c-api/time.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,31 @@ 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.
The functions return ``0`` on success, or set ``*result`` to ``0`` return
``-1`` on failure.
.. 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
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)
54 changes: 54 additions & 0 deletions Modules/_testcapi/time.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
}


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) {
PyErr_SetString(PyExc_RuntimeError, "PyTime_MonotonicRaw() failed");
return NULL;
}
assert(res == 0);
return pytime_as_float(t);
}


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


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) {
PyErr_SetString(PyExc_RuntimeError, "PyTime_PerfCounterRaw() failed");
return NULL;
}
assert(res == 0);
return pytime_as_float(t);
}


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


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) {
PyErr_SetString(PyExc_RuntimeError, "PyTime_TimeRaw() failed");
return NULL;
}
assert(res == 0);
return pytime_as_float(t);
}


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
79 changes: 55 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,37 @@ 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;
(void)PyTime_TimeRaw(&t);
return t;
}


int
_PyTime_TimeWithInfo(PyTime_t *t, _Py_clock_info_t *info)
{
Expand Down Expand Up @@ -1140,6 +1152,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 +1241,37 @@ 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;
(void)PyTime_MonotonicRaw(&t);
return t;
}


int
_PyTime_MonotonicWithInfo(PyTime_t *tp, _Py_clock_info_t *info)
{
Expand All @@ -1262,17 +1286,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

0 comments on commit 4ef776b

Please sign in to comment.