summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2024-05-01 18:05:01 (GMT)
committerGitHub <noreply@github.com>2024-05-01 18:05:01 (GMT)
commitb52c753e0fb8b87c7a0e3f1c72acd18327d8d16b (patch)
tree0ffb22ddef0675046cd44d9aec0361b5fce0f4cd
parenta8bcf3ec3284f042dab9cad676951092fb1f9890 (diff)
downloadcpython-b52c753e0fb8b87c7a0e3f1c72acd18327d8d16b.zip
cpython-b52c753e0fb8b87c7a0e3f1c72acd18327d8d16b.tar.gz
cpython-b52c753e0fb8b87c7a0e3f1c72acd18327d8d16b.tar.bz2
gh-110850: Add PyTime_TimeRaw() function (#118394)
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. Co-authored-by: Petr Viktorin <encukou@gmail.com>
-rw-r--r--Doc/c-api/time.rst29
-rw-r--r--Doc/whatsnew/3.13.rst12
-rw-r--r--Include/cpython/pytime.h4
-rw-r--r--Lib/test/test_capi/test_time.py19
-rw-r--r--Misc/NEWS.d/next/C API/2024-04-29-17-19-07.gh-issue-110850.vcpLn1.rst7
-rw-r--r--Modules/_testcapi/time.c60
-rw-r--r--Python/pytime.c93
7 files changed, 189 insertions, 35 deletions
diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst
index 7791cdb..5cfdef7 100644
--- a/Doc/c-api/time.rst
+++ b/Doc/c-api/time.rst
@@ -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
--------------------
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index ee50eff..5a16955 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -1901,9 +1901,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`.)
diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h
index d824470..5c68110 100644
--- a/Include/cpython/pytime.h
+++ b/Include/cpython/pytime.h
@@ -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
diff --git a/Lib/test/test_capi/test_time.py b/Lib/test/test_capi/test_time.py
index 10b7fbf..17ebd7c 100644
--- a/Lib/test/test_capi/test_time.py
+++ b/Lib/test/test_capi/test_time.py
@@ -18,11 +18,6 @@ class CAPITest(unittest.TestCase):
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):
@@ -58,14 +53,22 @@ class CAPITest(unittest.TestCase):
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)
diff --git a/Misc/NEWS.d/next/C API/2024-04-29-17-19-07.gh-issue-110850.vcpLn1.rst b/Misc/NEWS.d/next/C API/2024-04-29-17-19-07.gh-issue-110850.vcpLn1.rst
new file mode 100644
index 0000000..786da01
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2024-04-29-17-19-07.gh-issue-110850.vcpLn1.rst
@@ -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.
diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c
index 68f082b..464cf5c 100644
--- a/Modules/_testcapi/time.c
+++ b/Modules/_testcapi/time.c
@@ -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);
@@ -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);
@@ -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);
@@ -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},
};
diff --git a/Python/pytime.c b/Python/pytime.c
index d5b3804..12b36bb 100644
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -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;
@@ -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)
{
@@ -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) {
@@ -1225,22 +1248,21 @@ 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;
}
@@ -1248,6 +1270,22 @@ PyTime_Monotonic(PyTime_t *result)
}
+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)
{
@@ -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();
}