]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-110850: Add PyTime_TimeRaw() function (#118394)
authorVictor Stinner <vstinner@python.org>
Wed, 1 May 2024 18:05:01 +0000 (20:05 +0200)
committerGitHub <noreply@github.com>
Wed, 1 May 2024 18:05:01 +0000 (18:05 +0000)
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>
Doc/c-api/time.rst
Doc/whatsnew/3.13.rst
Include/cpython/pytime.h
Lib/test/test_capi/test_time.py
Misc/NEWS.d/next/C API/2024-04-29-17-19-07.gh-issue-110850.vcpLn1.rst [new file with mode: 0644]
Modules/_testcapi/time.c
Python/pytime.c

index 7791cdb17810550e434aa3e69f995e469ee5a9b4..5cfdef71b3e191e716a05b8f8e68931a2e41242b 100644 (file)
@@ -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
 --------------------
 
index ee50effd662f12e1a79f8f4a25bad412b8c8d4d7..5a169556066421abe7aabaff90c72c8a5674a01a 100644 (file)
@@ -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`.)
 
index d8244700d614ceb31b687a269c393351b9b84654..5c68110aeedb867ba90c8cab047fed1489e083ff 100644 (file)
@@ -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
index 10b7fbf2c372a3b9877abfdd69f853b531755768..17ebd7c1962d483bd517ee12cabd77e9c3a622f4 100644 (file)
@@ -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 (file)
index 0000000..786da01
--- /dev/null
@@ -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.
index 68f082bf3f3d88ad25315e27a2b5bd75690bd79b..464cf5c3125012efc6703b3576095ba3c4b849d7 100644 (file)
@@ -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},
 };
 
index d5b38047b6db31a76ad10f05a50a190a05821e49..12b36bbc881f9ad3d7e47c16dd6b7f9096515def 100644 (file)
@@ -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();
 }