From 4a8657cb35f92797783e7a2033a554a4ec070cc5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 16 Nov 2023 00:38:37 +0100 Subject: [PATCH 01/20] gh-110850: Add PyTime_t C API Add PyTime_t API: * PyTime_t type. * PyTime_MIN and PyTime_MAX constants. * PyTime_AsSecondsDouble(), PyTime_Monotonic(), PyTime_PerfCounter() and PyTime_GetSystemClock() functions. Changes: * Add Include/cpython/pytime.h header. * Add Modules/_testcapi/time.c and Lib/test/test_capi/test_time.py tests on these new functions. * Rename _PyTime_GetMonotonicClock() to PyTime_Monotonic(). * Rename _PyTime_GetPerfCounter() to PyTime_PerfCounter(). * Rename _PyTime_GetSystemClock() to PyTime_Time(). * Rename _PyTime_AsSecondsDouble() to PyTime_AsSecondsDouble(). * Rename _PyTime_MIN to PyTime_MIN. * Rename _PyTime_MAX to PyTime_MAX. --- Doc/c-api/time.rst | 103 +++++++++++++++ Doc/c-api/utilities.rst | 1 + Doc/conf.py | 13 +- Doc/whatsnew/3.13.rst | 10 ++ Include/Python.h | 1 + Include/cpython/pytime.h | 23 ++++ Include/internal/pycore_time.h | 124 +++++++----------- Lib/test/test_capi/test_time.py | 71 ++++++++++ Lib/test/test_time.py | 17 +-- ...-11-16-02-07-48.gh-issue-110850.DQGNfF.rst | 9 ++ Modules/Setup.stdlib.in | 2 +- Modules/_randommodule.c | 6 +- Modules/_testcapi/parts.h | 1 + Modules/_testcapi/time.c | 94 +++++++++++++ Modules/_testcapimodule.c | 3 + Modules/_testinternalcapi/pytime.c | 16 --- PCbuild/_testcapi.vcxproj | 1 + PCbuild/_testcapi.vcxproj.filters | 3 + Python/pytime.c | 72 +++++----- 19 files changed, 419 insertions(+), 151 deletions(-) create mode 100644 Doc/c-api/time.rst create mode 100644 Include/cpython/pytime.h create mode 100644 Lib/test/test_capi/test_time.py create mode 100644 Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst create mode 100644 Modules/_testcapi/time.c diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst new file mode 100644 index 00000000000000..adb122db576d9b --- /dev/null +++ b/Doc/c-api/time.rst @@ -0,0 +1,103 @@ +.. highlight:: c + +PyTime C API +============ + +.. versionadded:: 3.13 + +PyTime API +---------- + +The PyTime_t API is written to use timestamp and timeout values stored in +various formats and to read clocks with a resolution of one nanosecond. + +The :c:type:`PyTime_t` type is signed to support negative timestamps. The +supported range is around [-292.3 years; +292.3 years]. Using the Unix epoch +(January 1st, 1970) as reference, the supported date range is around +[1677-09-21; 2262-04-11]. + + +Types +----- + +.. c:type:: PyTime_t + + Timestamp type with subsecond precision: 64-bit signed integer. + + This type can be used to store a duration. Indirectly, it can be used to + store a date relative to a reference date, such as the UNIX epoch. + + +Constants +--------- + +.. c:var:: PyTime_t PyTime_MIN + + Minimum value of the :c:type:`PyTime_t` type. + + :c:var:`PyTime_MIN` nanoseconds is around -292.3 years. + +.. c:var:: PyTime_t PyTime_MAX + + Maximum value of the :c:type:`PyTime_t` type. + + :c:var:`PyTime_MAX` nanoseconds is around +292.3 years. + + +Functions +--------- + +.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t) + + Convert a timestamp to a number of seconds as a C :c:expr:`double`. + + The function cannot fail. + + +.. c:function:: PyTime_t PyTime_Monotonic(void) + + Get the monotonic clock: clock that cannot go backwards. + + The monotonic clock is not affected by system clock updates. The reference + point of the returned value is undefined, so that only the difference + between the results of consecutive calls is valid. + + If reading the clock fails, silently ignore the error and return 0. + + On integer overflow, silently ignore the overflow and clamp the clock to + the ``[PyTime_MIN; PyTime_MAX]`` range. + + See also the :func:`time.monotonic` function. + + +.. c:function:: PyTime_t PyTime_PerfCounter(void) + + Get the performance counter: clock with the highest available resolution to + measure a short duration. + + The performance counter does include time elapsed during sleep and is + system-wide. The reference point of the returned value is undefined, so that + only the difference between the results of two calls is valid. + + If reading the clock fails, silently ignore the error and return 0. + + On integer overflow, silently ignore the overflow and clamp the clock to + the ``[PyTime_MIN; PyTime_MAX]`` range. + + See also the :func:`time.perf_counter` function. + + +.. c:function:: PyTime_t PyTime_Time(void) + + Get the system clock. + + The system clock can be changed automatically (e.g. by a NTP daemon) or + manually by the system administrator. So it can also go backward. Use + :c:func:`PyTime_Monotonic` to use a monotonic clock. + + If reading the clock fails, silently ignore the error and return ``0``. + + On integer overflow, silently ignore the overflow and clamp the clock to + the ``[PyTime_MIN; PyTime_MAX]`` range. + + See also the :func:`time.time` function. diff --git a/Doc/c-api/utilities.rst b/Doc/c-api/utilities.rst index 48ae54acebe887..9d0abf440f791d 100644 --- a/Doc/c-api/utilities.rst +++ b/Doc/c-api/utilities.rst @@ -20,4 +20,5 @@ and parsing function arguments and constructing Python values from C values. hash.rst reflection.rst codec.rst + time.rst perfmaps.rst diff --git a/Doc/conf.py b/Doc/conf.py index c2d57696aeeaa3..aa7f85bc1b3efa 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -135,11 +135,14 @@ ('c:type', 'wchar_t'), ('c:type', '__int64'), ('c:type', 'unsigned __int64'), + ('c:type', 'double'), # Standard C structures ('c:struct', 'in6_addr'), ('c:struct', 'in_addr'), ('c:struct', 'stat'), ('c:struct', 'statvfs'), + ('c:struct', 'timeval'), + ('c:struct', 'timespec'), # Standard C macros ('c:macro', 'LLONG_MAX'), ('c:macro', 'LLONG_MIN'), @@ -269,12 +272,12 @@ ('py:meth', 'index'), # list.index, tuple.index, etc. ] -# gh-106948: Copy standard C types declared in the "c:type" domain to the -# "c:identifier" domain, since "c:function" markup looks for types in the -# "c:identifier" domain. Use list() to not iterate on items which are being -# added +# gh-106948: Copy standard C types declared in the "c:type" domain and C +# structures declared in the "c:struct" domain to the "c:identifier" domain, +# since "c:function" markup looks for types in the "c:identifier" domain. Use +# list() to not iterate on items which are being added for role, name in list(nitpick_ignore): - if role == 'c:type': + if role in ('c:type', 'c:struct'): nitpick_ignore.append(('c:identifier', name)) del role, name diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 50a2a69c75ac70..6bf6308eed8947 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1501,6 +1501,16 @@ New Features * Add :c:func:`Py_HashPointer` function to hash a pointer. (Contributed by Victor Stinner in :gh:`111545`.) +* Add PyTime C API: + + * :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. + + (Contributed by Victor Stinner in :gh:`110850`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/Python.h b/Include/Python.h index 196751c3201e62..01fc45137a17bb 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -97,6 +97,7 @@ #include "weakrefobject.h" #include "structseq.h" #include "cpython/picklebufobject.h" +#include "cpython/pytime.h" #include "codecs.h" #include "pyerrors.h" #include "pythread.h" diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h new file mode 100644 index 00000000000000..4179c15a4e6722 --- /dev/null +++ b/Include/cpython/pytime.h @@ -0,0 +1,23 @@ +// PyTime_t C API: see Doc/c-api/time.rst for the documentation. + +#ifndef Py_LIMITED_API +#ifndef Py_PYTIME_H +#define Py_PYTIME_H +#ifdef __cplusplus +extern "C" { +#endif + +typedef int64_t PyTime_t; +#define PyTime_MIN INT64_MIN +#define PyTime_MAX INT64_MAX + +PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t); +PyAPI_FUNC(PyTime_t) PyTime_Monotonic(void); +PyAPI_FUNC(PyTime_t) PyTime_PerfCounter(void); +PyAPI_FUNC(PyTime_t) PyTime_Time(void); + +#ifdef __cplusplus +} +#endif +#endif /* Py_PYTIME_H */ +#endif /* Py_LIMITED_API */ diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index dabbd7b41556cd..6df69245be67e7 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -1,34 +1,34 @@ -// The _PyTime_t API is written to use timestamp and timeout values stored in -// various formats and to read clocks. +// Internal PyTime_t C API: see Doc/c-api/time.rst for the documentation. // -// The _PyTime_t type is an integer to support directly common arithmetic -// operations like t1 + t2. +// The PyTime_t type is an integer to support directly common arithmetic +// operations such as t1 + t2. // -// The _PyTime_t API supports a resolution of 1 nanosecond. The _PyTime_t type -// is signed to support negative timestamps. The supported range is around -// [-292.3 years; +292.3 years]. Using the Unix epoch (January 1st, 1970), the -// supported date range is around [1677-09-21; 2262-04-11]. +// Time formats: // -// Formats: -// -// * seconds -// * seconds as a floating pointer number (C double) -// * milliseconds (10^-3 seconds) -// * microseconds (10^-6 seconds) -// * 100 nanoseconds (10^-7 seconds) -// * nanoseconds (10^-9 seconds) -// * timeval structure, 1 microsecond resolution (10^-6 seconds) -// * timespec structure, 1 nanosecond resolution (10^-9 seconds) +// * Seconds. +// * Seconds as a floating point number (C double). +// * Milliseconds (10^-3 seconds). +// * Microseconds (10^-6 seconds). +// * 100 nanoseconds (10^-7 seconds), used on Windows. +// * Nanoseconds (10^-9 seconds). +// * timeval structure, 1 microsecond (10^-6 seconds). +// * timespec structure, 1 nanosecond (10^-9 seconds). // // Integer overflows are detected and raise OverflowError. Conversion to a -// resolution worse than 1 nanosecond is rounded correctly with the requested -// rounding mode. There are 4 rounding modes: floor (towards -inf), ceiling -// (towards +inf), half even and up (away from zero). +// resolution larger than 1 nanosecond is rounded correctly with the requested +// rounding mode. Available rounding modes: +// +// * Round towards minus infinity (-inf). For example, used to read a clock. +// * Round towards infinity (+inf). For example, used for timeout to wait "at +// least" N seconds. +// * Round to nearest with ties going to nearest even integer. For example, used +// to round from a Python float. +// * Round away from zero. For example, used for timeout. // -// Some functions clamp the result in the range [_PyTime_MIN; _PyTime_MAX], so -// the caller doesn't have to handle errors and doesn't need to hold the GIL. -// For example, _PyTime_Add(t1, t2) computes t1+t2 and clamp the result on -// overflow. +// Some functions clamp the result in the range [PyTime_MIN; PyTime_MAX]. The +// caller doesn't have to handle errors and so doesn't need to hold the GIL to +// handle exceptions. For example, _PyTime_Add(t1, t2) computes t1+t2 and +// clamps the result on overflow. // // Clocks: // @@ -36,10 +36,11 @@ // * Monotonic clock // * Performance counter // -// Operations like (t * k / q) with integers are implemented in a way to reduce -// the risk of integer overflow. Such operation is used to convert a clock -// value expressed in ticks with a frequency to _PyTime_t, like -// QueryPerformanceCounter() with QueryPerformanceFrequency(). +// Internally, operations like (t * k / q) with integers are implemented in a +// way to reduce the risk of integer overflow. Such operation is used to convert a +// clock value expressed in ticks with a frequency to PyTime_t, like +// QueryPerformanceCounter() with QueryPerformanceFrequency() on Windows. + #ifndef Py_INTERNAL_TIME_H #define Py_INTERNAL_TIME_H @@ -56,14 +57,7 @@ extern "C" { struct timeval; #endif -// _PyTime_t: Python timestamp with subsecond precision. It can be used to -// store a duration, and so indirectly a date (related to another date, like -// UNIX epoch). -typedef int64_t _PyTime_t; -// _PyTime_MIN nanoseconds is around -292.3 years -#define _PyTime_MIN INT64_MIN -// _PyTime_MAX nanoseconds is around +292.3 years -#define _PyTime_MAX INT64_MAX +typedef PyTime_t _PyTime_t; #define _SIZEOF_PYTIME_T 8 typedef enum { @@ -147,7 +141,7 @@ PyAPI_FUNC(_PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(_PyTime_t ns); // Create a timestamp from a number of microseconds. -// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Clamp to [PyTime_MIN; PyTime_MAX] on overflow. extern _PyTime_t _PyTime_FromMicrosecondsClamp(_PyTime_t us); // Create a timestamp from nanoseconds (Python int). @@ -169,10 +163,6 @@ PyAPI_FUNC(int) _PyTime_FromMillisecondsObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t round); -// Convert a timestamp to a number of seconds as a C double. -// Export for '_socket' shared extension. -PyAPI_FUNC(double) _PyTime_AsSecondsDouble(_PyTime_t t); - // Convert timestamp to a number of milliseconds (10^-3 seconds). // Export for '_ssl' shared extension. PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t, @@ -250,7 +240,7 @@ PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts); #endif -// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. extern _PyTime_t _PyTime_Add(_PyTime_t t1, _PyTime_t t2); // Structure used by time.get_clock_info() @@ -261,16 +251,6 @@ typedef struct { double resolution; } _Py_clock_info_t; -// Get the current time from the system clock. -// -// If the internal clock fails, silently ignore the error and return 0. -// On integer overflow, silently ignore the overflow and clamp the clock to -// [_PyTime_MIN; _PyTime_MAX]. -// -// Use _PyTime_GetSystemClockWithInfo() to check for failure. -// Export for '_random' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void); - // Get the current time from the system clock. // On success, set *t and *info (if not NULL), and return 0. // On error, raise an exception and return -1. @@ -278,19 +258,6 @@ extern int _PyTime_GetSystemClockWithInfo( _PyTime_t *t, _Py_clock_info_t *info); -// 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 -// results of consecutive calls is valid. -// -// If the internal clock fails, silently ignore the error and return 0. -// On integer overflow, silently ignore the overflow and clamp the clock to -// [_PyTime_MIN; _PyTime_MAX]. -// -// Use _PyTime_GetMonotonicClockWithInfo() to check for failure. -// Export for '_random' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(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 @@ -315,17 +282,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); -// Get the performance counter: clock with the highest available resolution to -// measure a short duration. -// -// If the internal clock fails, silently ignore the error and return 0. -// On integer overflow, silently ignore the overflow and clamp the clock to -// [_PyTime_MIN; _PyTime_MAX]. -// -// Use _PyTime_GetPerfCounterWithInfo() to check for failure. -// Export for '_lsprof' shared extension. -PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void); - // Get the performance counter: clock with the highest available resolution to // measure a short duration. // @@ -336,14 +292,24 @@ extern int _PyTime_GetPerfCounterWithInfo( _PyTime_t *t, _Py_clock_info_t *info); +// Alias for backward compatibility +#define _PyTime_MIN PyTime_MIN +#define _PyTime_MAX PyTime_MAX +#define _PyTime_AsSecondsDouble PyTime_AsSecondsDouble +#define _PyTime_GetMonotonicClock PyTime_Monotonic +#define _PyTime_GetPerfCounter PyTime_PerfCounter +#define _PyTime_GetSystemClock PyTime_Time + + +// --- _PyDeadline ----------------------------------------------------------- // Create a deadline. -// Pseudo code: _PyTime_GetMonotonicClock() + timeout. +// Pseudo code: PyTime_Monotonic() + timeout. // Export for '_ssl' shared extension. PyAPI_FUNC(_PyTime_t) _PyDeadline_Init(_PyTime_t timeout); // Get remaining time from a deadline. -// Pseudo code: deadline - _PyTime_GetMonotonicClock(). +// Pseudo code: deadline - PyTime_Monotonic(). // Export for '_ssl' shared extension. PyAPI_FUNC(_PyTime_t) _PyDeadline_Get(_PyTime_t deadline); diff --git a/Lib/test/test_capi/test_time.py b/Lib/test/test_capi/test_time.py new file mode 100644 index 00000000000000..10b7fbf2c372a3 --- /dev/null +++ b/Lib/test/test_capi/test_time.py @@ -0,0 +1,71 @@ +import time +import unittest +from test.support import import_helper +_testcapi = import_helper.import_module('_testcapi') + + +PyTime_MIN = _testcapi.PyTime_MIN +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. +CLOCK_RES = 0.050 + + +class CAPITest(unittest.TestCase): + def test_min_max(self): + # PyTime_t is just int64_t + 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): + if abs(ns) % SEC_TO_NS == 0: + return float(ns // SEC_TO_NS) + else: + return float(ns) / SEC_TO_NS + + seconds = ( + 0, + 1, + DAY_TO_SEC, + 365 * DAY_TO_SEC, + ) + values = { + PyTime_MIN, + PyTime_MIN + 1, + PyTime_MAX - 1, + PyTime_MAX, + } + for second in seconds: + ns = second * SEC_TO_NS + values.add(ns) + # test nanosecond before/after to test rounding + values.add(ns - 1) + values.add(ns + 1) + for ns in list(values): + if (-ns) > PyTime_MAX: + continue + values.add(-ns) + for ns in sorted(values): + with self.subTest(ns=ns): + self.assertEqual(_testcapi.PyTime_AsSecondsDouble(ns), + ns_to_sec(ns)) + + def test_monotonic(self): + # Test PyTime_Monotonic() + self.check_clock(_testcapi.PyTime_Monotonic, time.monotonic) + + def test_perf_counter(self): + # Test PyTime_PerfCounter() + self.check_clock(_testcapi.PyTime_PerfCounter, time.perf_counter) + + def test_time(self): + # Test PyTime_time() + self.check_clock(_testcapi.PyTime_Time, time.time) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 3b5640abdb6b89..a0aeea515afbd6 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -43,8 +43,8 @@ class _PyTime(enum.IntEnum): ROUND_UP = 3 # _PyTime_t is int64_t -_PyTime_MIN = -2 ** 63 -_PyTime_MAX = 2 ** 63 - 1 +PyTime_MIN = -2 ** 63 +PyTime_MAX = 2 ** 63 - 1 # Rounding modes supported by PyTime ROUNDING_MODES = ( @@ -934,7 +934,7 @@ def test_FromSecondsObject(self): _PyTime_FromSecondsObject(float('nan'), time_rnd) def test_AsSecondsDouble(self): - from _testinternalcapi import _PyTime_AsSecondsDouble + from _testcapi import PyTime_AsSecondsDouble def float_converter(ns): if abs(ns) % SEC_TO_NS == 0: @@ -942,15 +942,10 @@ def float_converter(ns): else: return float(ns) / SEC_TO_NS - self.check_int_rounding(lambda ns, rnd: _PyTime_AsSecondsDouble(ns), + self.check_int_rounding(lambda ns, rnd: PyTime_AsSecondsDouble(ns), float_converter, NS_TO_SEC) - # test nan - for time_rnd, _ in ROUNDING_MODES: - with self.assertRaises(TypeError): - _PyTime_AsSecondsDouble(float('nan')) - def create_decimal_converter(self, denominator): denom = decimal.Decimal(denominator) @@ -1009,7 +1004,7 @@ def test_AsTimeval_clamp(self): tv_sec_max = self.time_t_max tv_sec_min = self.time_t_min - for t in (_PyTime_MIN, _PyTime_MAX): + for t in (PyTime_MIN, PyTime_MAX): ts = _PyTime_AsTimeval_clamp(t, _PyTime.ROUND_CEILING) with decimal.localcontext() as context: context.rounding = decimal.ROUND_CEILING @@ -1028,7 +1023,7 @@ def test_AsTimeval_clamp(self): def test_AsTimespec_clamp(self): from _testinternalcapi import _PyTime_AsTimespec_clamp - for t in (_PyTime_MIN, _PyTime_MAX): + for t in (PyTime_MIN, PyTime_MAX): ts = _PyTime_AsTimespec_clamp(t) tv_sec, tv_nsec = divmod(t, NS_TO_SEC) if self.time_t_max < tv_sec: diff --git a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst new file mode 100644 index 00000000000000..50c6e9c4275b62 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst @@ -0,0 +1,9 @@ +Add PyTime C API: + +* :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. + +Patch by Victor Stinner. diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 8a65a9cffb1b9d..e98775a4808765 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -162,7 +162,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/time.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 4403e1d132c057..80169de9ca93d7 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -262,10 +262,10 @@ random_seed_urandom(RandomObject *self) static void random_seed_time_pid(RandomObject *self) { - _PyTime_t now; + PyTime_t now; uint32_t key[5]; - now = _PyTime_GetSystemClock(); + now = PyTime_Time(); key[0] = (uint32_t)(now & 0xffffffffU); key[1] = (uint32_t)(now >> 32); @@ -277,7 +277,7 @@ random_seed_time_pid(RandomObject *self) key[2] = 0; #endif - now = _PyTime_GetMonotonicClock(); + now = PyTime_Monotonic(); key[3] = (uint32_t)(now & 0xffffffffU); key[4] = (uint32_t)(now >> 32); diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 29817edd69b134..e8cfb2423500d4 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -59,6 +59,7 @@ int _PyTestCapi_Init_Immortal(PyObject *module); int _PyTestCapi_Init_GC(PyObject *module); int _PyTestCapi_Init_Sys(PyObject *module); int _PyTestCapi_Init_Hash(PyObject *module); +int _PyTestCapi_Init_Time(PyObject *module); int _PyTestCapi_Init_VectorcallLimited(PyObject *module); int _PyTestCapi_Init_HeaptypeRelative(PyObject *module); diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c new file mode 100644 index 00000000000000..b0389402ae7aa4 --- /dev/null +++ b/Modules/_testcapi/time.c @@ -0,0 +1,94 @@ +#include "parts.h" + + +static int +pytime_from_nanoseconds(PyTime_t *tp, PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expect int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + long long nsec = PyLong_AsLongLong(obj); + if (nsec == -1 && PyErr_Occurred()) { + return -1; + } + + Py_BUILD_ASSERT(sizeof(long long) == sizeof(PyTime_t)); + *tp = (PyTime_t)nsec; + return 0; +} + + +static PyObject * +test_pytime_assecondsdouble(PyObject *Py_UNUSED(self), PyObject *args) +{ + PyObject *obj; + if (!PyArg_ParseTuple(args, "O", &obj)) { + return NULL; + } + PyTime_t ts; + if (pytime_from_nanoseconds(&ts, obj) < 0) { + return NULL; + } + double d = PyTime_AsSecondsDouble(ts); + return PyFloat_FromDouble(d); +} + + +static PyObject* +pytime_as_float(PyTime_t t) +{ + return PyFloat_FromDouble(PyTime_AsSecondsDouble(t)); +} + + + +static PyObject* +test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t = PyTime_Monotonic(); + return pytime_as_float(t); +} + + +static PyObject* +test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t = PyTime_PerfCounter(); + return pytime_as_float(t); +} + + +static PyObject* +test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t = PyTime_Time(); + return pytime_as_float(t); +} + + +static PyMethodDef test_methods[] = { + {"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS}, + {"PyTime_Monotonic", test_pytime_monotonic, METH_NOARGS}, + {"PyTime_PerfCounter", test_pytime_perf_counter, METH_NOARGS}, + {"PyTime_Time", test_pytime_time, METH_NOARGS}, + {NULL}, +}; + +int +_PyTestCapi_Init_Time(PyObject *m) +{ + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + Py_BUILD_ASSERT(sizeof(long long) == sizeof(PyTime_t)); + if (PyModule_AddObject(m, "PyTime_MIN", PyLong_FromLongLong(PyTime_MIN)) < 0) { + return 1; + } + if (PyModule_AddObject(m, "PyTime_MAX", PyLong_FromLongLong(PyTime_MAX)) < 0) { + return 1; + } + return 0; +} diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index e67de3eeb6e17e..b03f871b089c8a 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4107,6 +4107,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Hash(m) < 0) { return NULL; } + if (_PyTestCapi_Init_Time(m) < 0) { + return NULL; + } PyState_AddModule(m, &_testcapimodule); return m; diff --git a/Modules/_testinternalcapi/pytime.c b/Modules/_testinternalcapi/pytime.c index 2b5f9eb0ef2851..f0f758ea032df8 100644 --- a/Modules/_testinternalcapi/pytime.c +++ b/Modules/_testinternalcapi/pytime.c @@ -52,21 +52,6 @@ test_pytime_fromsecondsobject(PyObject *self, PyObject *args) return _PyTime_AsNanosecondsObject(ts); } -static PyObject * -test_pytime_assecondsdouble(PyObject *self, PyObject *args) -{ - PyObject *obj; - if (!PyArg_ParseTuple(args, "O", &obj)) { - return NULL; - } - _PyTime_t ts; - if (_PyTime_FromNanosecondsObject(&ts, obj) < 0) { - return NULL; - } - double d = _PyTime_AsSecondsDouble(ts); - return PyFloat_FromDouble(d); -} - static PyObject * test_PyTime_AsTimeval(PyObject *self, PyObject *args) { @@ -254,7 +239,6 @@ test_pytime_object_to_timespec(PyObject *self, PyObject *args) static PyMethodDef TestMethods[] = { {"_PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS}, {"_PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS}, - {"_PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS}, #ifdef HAVE_CLOCK_GETTIME {"_PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS}, {"_PyTime_AsTimespec_clamp", test_PyTime_AsTimespec_clamp, METH_VARARGS}, diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 6911aacab29b97..66df0a61b5b5a6 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -125,6 +125,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index 6059959bb9a040..651eb1d6ba0b7f 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -105,6 +105,9 @@ Source Files + + Source Files + Source Files diff --git a/Python/pytime.c b/Python/pytime.c index 77cb95f8feb179..e0bcecf0aa25d8 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -50,7 +50,7 @@ # error "time_t is not a two's complement integer type" #endif -#if _PyTime_MIN + _PyTime_MAX != -1 +#if PyTime_MIN + PyTime_MAX != -1 # error "_PyTime_t is not a two's complement integer type" #endif @@ -124,16 +124,16 @@ pytime_as_nanoseconds(_PyTime_t t) } -// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline int pytime_add(_PyTime_t *t1, _PyTime_t t2) { - if (t2 > 0 && *t1 > _PyTime_MAX - t2) { - *t1 = _PyTime_MAX; + if (t2 > 0 && *t1 > PyTime_MAX - t2) { + *t1 = PyTime_MAX; return -1; } - else if (t2 < 0 && *t1 < _PyTime_MIN - t2) { - *t1 = _PyTime_MIN; + else if (t2 < 0 && *t1 < PyTime_MIN - t2) { + *t1 = PyTime_MIN; return -1; } else { @@ -156,7 +156,7 @@ pytime_mul_check_overflow(_PyTime_t a, _PyTime_t b) { if (b != 0) { assert(b > 0); - return ((a < _PyTime_MIN / b) || (_PyTime_MAX / b < a)); + return ((a < PyTime_MIN / b) || (PyTime_MAX / b < a)); } else { return 0; @@ -164,13 +164,13 @@ pytime_mul_check_overflow(_PyTime_t a, _PyTime_t b) } -// Compute t * k. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Compute t * k. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline int pytime_mul(_PyTime_t *t, _PyTime_t k) { assert(k >= 0); if (pytime_mul_check_overflow(*t, k)) { - *t = (*t >= 0) ? _PyTime_MAX : _PyTime_MIN; + *t = (*t >= 0) ? PyTime_MAX : PyTime_MIN; return -1; } else { @@ -180,7 +180,7 @@ pytime_mul(_PyTime_t *t, _PyTime_t k) } -// Compute t * k. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +// Compute t * k. Clamp to [PyTime_MIN; PyTime_MAX] on overflow. static inline _PyTime_t _PyTime_Mul(_PyTime_t t, _PyTime_t k) { @@ -459,12 +459,12 @@ _PyTime_FromSeconds(int seconds) /* ensure that integer overflow cannot happen, int type should have 32 bits, whereas _PyTime_t type has at least 64 bits (SEC_TO_NS takes 30 bits). */ - static_assert(INT_MAX <= _PyTime_MAX / SEC_TO_NS, "_PyTime_t overflow"); - static_assert(INT_MIN >= _PyTime_MIN / SEC_TO_NS, "_PyTime_t underflow"); + static_assert(INT_MAX <= PyTime_MAX / SEC_TO_NS, "_PyTime_t overflow"); + static_assert(INT_MIN >= PyTime_MIN / SEC_TO_NS, "_PyTime_t underflow"); _PyTime_t t = (_PyTime_t)seconds; - assert((t >= 0 && t <= _PyTime_MAX / SEC_TO_NS) - || (t < 0 && t >= _PyTime_MIN / SEC_TO_NS)); + assert((t >= 0 && t <= PyTime_MAX / SEC_TO_NS) + || (t < 0 && t >= PyTime_MIN / SEC_TO_NS)); t *= SEC_TO_NS; return pytime_from_nanoseconds(t); } @@ -587,7 +587,7 @@ pytime_from_double(_PyTime_t *tp, double value, _PyTime_round_t round, d = pytime_round(d, round); /* See comments in pytime_double_to_denominator */ - if (!((double)_PyTime_MIN <= d && d < -(double)_PyTime_MIN)) { + if (!((double)PyTime_MIN <= d && d < -(double)PyTime_MIN)) { pytime_time_t_overflow(); return -1; } @@ -649,12 +649,12 @@ _PyTime_FromMillisecondsObject(_PyTime_t *tp, PyObject *obj, _PyTime_round_t rou double -_PyTime_AsSecondsDouble(_PyTime_t t) +PyTime_AsSecondsDouble(PyTime_t t) { /* volatile avoids optimization changing how numbers are rounded */ volatile double d; - _PyTime_t ns = pytime_as_nanoseconds(t); + PyTime_t ns = pytime_as_nanoseconds(t); if (ns % SEC_TO_NS == 0) { /* Divide using integers to avoid rounding issues on the integer part. 1e-9 cannot be stored exactly in IEEE 64-bit. */ @@ -695,7 +695,7 @@ pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) assert(k > 1); if (t >= 0) { // Don't use (t + k - 1) / k to avoid integer overflow - // if t is equal to _PyTime_MAX + // if t is equal to PyTime_MAX _PyTime_t q = t / k; if (t % k) { q += 1; @@ -704,7 +704,7 @@ pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) } else { // Don't use (t - (k - 1)) / k to avoid integer overflow - // if t is equals to _PyTime_MIN. + // if t is equals to PyTime_MIN. _PyTime_t q = t / k; if (t % k) { q -= 1; @@ -759,7 +759,7 @@ pytime_divide(const _PyTime_t t, const _PyTime_t k, // Compute (t / k, t % k) in (pq, pr). // Make sure that 0 <= pr < k. // Return 0 on success. -// Return -1 on underflow and store (_PyTime_MIN, 0) in (pq, pr). +// Return -1 on underflow and store (PyTime_MIN, 0) in (pq, pr). static int pytime_divmod(const _PyTime_t t, const _PyTime_t k, _PyTime_t *pq, _PyTime_t *pr) @@ -768,8 +768,8 @@ pytime_divmod(const _PyTime_t t, const _PyTime_t k, _PyTime_t q = t / k; _PyTime_t r = t % k; if (r < 0) { - if (q == _PyTime_MIN) { - *pq = _PyTime_MIN; + if (q == PyTime_MIN) { + *pq = PyTime_MIN; *pr = 0; return -1; } @@ -1037,10 +1037,10 @@ py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } -_PyTime_t -_PyTime_GetSystemClock(void) +PyTime_t +PyTime_Time(void) { - _PyTime_t t; + 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. @@ -1102,13 +1102,13 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) static_assert(sizeof(ticks) <= sizeof(_PyTime_t), "ULONGLONG is larger than _PyTime_t"); _PyTime_t t; - if (ticks <= (ULONGLONG)_PyTime_MAX) { + if (ticks <= (ULONGLONG)PyTime_MAX) { t = (_PyTime_t)ticks; } else { // GetTickCount64() maximum is larger than _PyTime_t maximum: // ULONGLONG is unsigned, whereas _PyTime_t is signed. - t = _PyTime_MAX; + t = PyTime_MAX; } int res = pytime_mul(&t, MS_TO_NS); @@ -1151,7 +1151,7 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) uint64_t uticks = mach_absolute_time(); // unsigned => signed - assert(uticks <= (uint64_t)_PyTime_MAX); + assert(uticks <= (uint64_t)PyTime_MAX); _PyTime_t ticks = (_PyTime_t)uticks; _PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); @@ -1216,10 +1216,10 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } -_PyTime_t -_PyTime_GetMonotonicClock(void) +PyTime_t +PyTime_Monotonic(void) { - _PyTime_t t; + PyTime_t t; if (py_get_monotonic_clock(&t, NULL, 0) < 0) { // If mach_timebase_info(), clock_gettime() or gethrtime() fails: // silently ignore the failure and return 0. @@ -1316,10 +1316,10 @@ _PyTime_GetPerfCounterWithInfo(_PyTime_t *t, _Py_clock_info_t *info) } -_PyTime_t -_PyTime_GetPerfCounter(void) +PyTime_t +PyTime_PerfCounter(void) { - _PyTime_t t; + PyTime_t t; int res; #ifdef MS_WINDOWS res = py_get_win_perf_counter(&t, NULL, 0); @@ -1405,7 +1405,7 @@ _PyTime_gmtime(time_t t, struct tm *tm) _PyTime_t _PyDeadline_Init(_PyTime_t timeout) { - _PyTime_t now = _PyTime_GetMonotonicClock(); + _PyTime_t now = PyTime_Monotonic(); return _PyTime_Add(now, timeout); } @@ -1413,6 +1413,6 @@ _PyDeadline_Init(_PyTime_t timeout) _PyTime_t _PyDeadline_Get(_PyTime_t deadline) { - _PyTime_t now = _PyTime_GetMonotonicClock(); + _PyTime_t now = PyTime_Monotonic(); return deadline - now; } From 3f60d675e6e0b4777d427089a435e1c643239265 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 15 Dec 2023 00:36:56 +0100 Subject: [PATCH 02/20] Address Sam's review: update doc --- Doc/c-api/time.rst | 55 ++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index adb122db576d9b..62ffdb9b69a56a 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -8,13 +8,8 @@ PyTime C API PyTime API ---------- -The PyTime_t API is written to use timestamp and timeout values stored in -various formats and to read clocks with a resolution of one nanosecond. - -The :c:type:`PyTime_t` type is signed to support negative timestamps. The -supported range is around [-292.3 years; +292.3 years]. Using the Unix epoch -(January 1st, 1970) as reference, the supported date range is around -[1677-09-21; 2262-04-11]. +The PyTime API provides access to system clocks and time conversion functions. +It is similar to the Python :mod:`time` module. Types @@ -22,10 +17,15 @@ Types .. c:type:: PyTime_t - Timestamp type with subsecond precision: 64-bit signed integer. + A timestamp or duration in nanoseconds represented as a 64-bit signed + integer. + + The reference point for timestamps depends on the clock used. For example, + :c:func:`PyTime_Time` returns timestamps relative to the UNIX epoch. - This type can be used to store a duration. Indirectly, it can be used to - store a date relative to a reference date, such as the UNIX epoch. + The supported range is around [-292.3 years; +292.3 years]. Using the Unix + epoch (January 1st, 1970) as reference, the supported date range is around + [1677-09-21; 2262-04-11]. Constants @@ -56,48 +56,35 @@ Functions .. c:function:: PyTime_t PyTime_Monotonic(void) - Get the monotonic clock: clock that cannot go backwards. - - The monotonic clock is not affected by system clock updates. The reference - point of the returned value is undefined, so that only the difference - between the results of consecutive calls is valid. + Return the value in nanoseconds of a monotonic clock, i.e. a clock + that cannot go backwards. Similar to :func:`time.monotonic_ns`; see + :func:`time.monotonic` for details. - If reading the clock fails, silently ignore the error and return 0. + If reading the clock fails, silently ignore the error and return ``0``. On integer overflow, silently ignore the overflow and clamp the clock to the ``[PyTime_MIN; PyTime_MAX]`` range. - See also the :func:`time.monotonic` function. - .. c:function:: PyTime_t PyTime_PerfCounter(void) - Get the performance counter: clock with the highest available resolution to - measure a short duration. + Return the value in nanoseconds of a performance counter, i.e. a + clock with the highest available resolution to measure a short duration. + Similar to :func:`time.perf_counter_ns`; see :func:`time.perf_counter` for + details. - The performance counter does include time elapsed during sleep and is - system-wide. The reference point of the returned value is undefined, so that - only the difference between the results of two calls is valid. - - If reading the clock fails, silently ignore the error and return 0. + If reading the clock fails, silently ignore the error and return ``0``. On integer overflow, silently ignore the overflow and clamp the clock to the ``[PyTime_MIN; PyTime_MAX]`` range. - See also the :func:`time.perf_counter` function. - .. c:function:: PyTime_t PyTime_Time(void) - Get the system clock. - - The system clock can be changed automatically (e.g. by a NTP daemon) or - manually by the system administrator. So it can also go backward. Use - :c:func:`PyTime_Monotonic` to use a monotonic clock. + Return the time in nanoseconds since the epoch_. Similar to + :func:`time.time_ns`; see :func:`time.time` for details. If reading the clock fails, silently ignore the error and return ``0``. On integer overflow, silently ignore the overflow and clamp the clock to the ``[PyTime_MIN; PyTime_MAX]`` range. - - See also the :func:`time.time` function. From a0c69bc2b79dff23711eddea21f416764c109f11 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 15 Dec 2023 17:27:38 +0100 Subject: [PATCH 03/20] Avoid reference to epoch_ --- Doc/c-api/time.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index 62ffdb9b69a56a..add8d481eb7a41 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -81,8 +81,8 @@ Functions .. c:function:: PyTime_t PyTime_Time(void) - Return the time in nanoseconds since the epoch_. Similar to - :func:`time.time_ns`; see :func:`time.time` for details. + Return the time in nanoseconds since January 1, 1970, 00:00:00 (UTC). + Similar to :func:`time.time_ns`; see :func:`time.time` for details. If reading the clock fails, silently ignore the error and return ``0``. From 8397a05f55c770b9cec134bac7826e2dc09b845e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 8 Feb 2024 13:46:33 +0100 Subject: [PATCH 04/20] Make the docs more concise & rename to "Clock C API" The `PyTime_` prefix is already used by datetime API, e.g. `PyDate_FromDate`. --- Doc/c-api/time.rst | 81 +++++++++++++++++++------------------------ Doc/whatsnew/3.13.rst | 2 +- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index add8d481eb7a41..f786826ae5a06c 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -5,12 +5,11 @@ PyTime C API .. versionadded:: 3.13 -PyTime API ----------- - -The PyTime API provides access to system clocks and time conversion functions. +The clock C API provides access to system clocks and time conversion functions. It is similar to the Python :mod:`time` module. +For C API related to the :mod:`datetime` module, see :ref:`datetimeobjects`. + Types ----- @@ -26,65 +25,55 @@ Types The supported range is around [-292.3 years; +292.3 years]. Using the Unix epoch (January 1st, 1970) as reference, the supported date range is around [1677-09-21; 2262-04-11]. + The exact limits are exposed as constants: + .. c:var:: PyTime_t PyTime_MIN -Constants ---------- - -.. c:var:: PyTime_t PyTime_MIN - - Minimum value of the :c:type:`PyTime_t` type. - - :c:var:`PyTime_MIN` nanoseconds is around -292.3 years. - -.. c:var:: PyTime_t PyTime_MAX - - Maximum value of the :c:type:`PyTime_t` type. + Minimum value of :c:type:`PyTime_t`. - :c:var:`PyTime_MAX` nanoseconds is around +292.3 years. + .. c:var:: PyTime_t PyTime_MAX + Maximum value of :c:type:`PyTime_t`. -Functions ---------- -.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t) - - Convert a timestamp to a number of seconds as a C :c:expr:`double`. - - The function cannot fail. +Clock Functions +--------------- +The following functions take a pointer to a :c:expr:`PyTime_t` that they +set to the value of a particular clock. +Details of each clock are given in the documentation of the corresponding +Python function. -.. c:function:: PyTime_t PyTime_Monotonic(void) +The functions return 0 on success, or -1 (with an exception set) on failure. - Return the value in nanoseconds of a monotonic clock, i.e. a clock - that cannot go backwards. Similar to :func:`time.monotonic_ns`; see - :func:`time.monotonic` for details. +On integer overflow, they set the :c:data:`PyExc_OverflowError` exception and +set :c:expr:`*result` to the value clamped to the ``[PyTime_MIN; PyTime_MAX]`` +range. +(On current systems, integer overflows are likely caused by misconfigured +system time.) - If reading the clock fails, silently ignore the error and return ``0``. +.. c:function:: int PyTime_Monotonic(PyTime_t *result) - On integer overflow, silently ignore the overflow and clamp the clock to - the ``[PyTime_MIN; PyTime_MAX]`` range. + Read the monotonic clock. + See :func:`time.monotonic` for important details on this clock. +.. c:function:: int PyTime_PerfCounter(PyTime_t *result) -.. c:function:: PyTime_t PyTime_PerfCounter(void) + Read the performance counter. + See :func:`time.perf_counter` for important details on this clock. - Return the value in nanoseconds of a performance counter, i.e. a - clock with the highest available resolution to measure a short duration. - Similar to :func:`time.perf_counter_ns`; see :func:`time.perf_counter` for - details. +.. c:function:: int PyTime_Time(PyTime_t *result) - If reading the clock fails, silently ignore the error and return ``0``. + Read the “wall clock” time. + See :func:`time.time` for details important on this clock. - On integer overflow, silently ignore the overflow and clamp the clock to - the ``[PyTime_MIN; PyTime_MAX]`` range. +Other functions +--------------- -.. c:function:: PyTime_t PyTime_Time(void) - - Return the time in nanoseconds since January 1, 1970, 00:00:00 (UTC). - Similar to :func:`time.time_ns`; see :func:`time.time` for details. +.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t) - If reading the clock fails, silently ignore the error and return ``0``. + Convert a timestamp to a number of seconds as a C :c:expr:`double`. - On integer overflow, silently ignore the overflow and clamp the clock to - the ``[PyTime_MIN; PyTime_MAX]`` range. + The function cannot fail, but note that :c:expr:`double` has limited + accuracy for large values. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 6bf6308eed8947..dd39b56920c662 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1501,7 +1501,7 @@ New Features * Add :c:func:`Py_HashPointer` function to hash a pointer. (Contributed by Victor Stinner in :gh:`111545`.) -* Add PyTime C API: +* Add clock C API: * :c:type:`PyTime_t` type. * :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants. From 793eb58976529720fbd09841148165c7867f3b02 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 9 Feb 2024 11:29:35 +0100 Subject: [PATCH 05/20] Doc: Make nanoseconds a detail; add PyTime_AsNanoseconds --- Doc/c-api/time.rst | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index f786826ae5a06c..ff7e4df43089b8 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -16,16 +16,24 @@ Types .. c:type:: PyTime_t - A timestamp or duration in nanoseconds represented as a 64-bit signed - integer. + A timestamp or duration. The reference point for timestamps depends on the clock used. For example, :c:func:`PyTime_Time` returns timestamps relative to the UNIX epoch. - The supported range is around [-292.3 years; +292.3 years]. Using the Unix - epoch (January 1st, 1970) as reference, the supported date range is around - [1677-09-21; 2262-04-11]. - The exact limits are exposed as constants: + ``PyTime_t`` is a signed integral type. It is possible to add and + subtract such values directly. + Keep in mind that C overflow rules do apply. + + ``PyTime_t`` can represent time with nanosecond or better resolution. + The unit used is an internal implementation detail; use + :c:func:`PyTime_AsNanoseconds` or :c:func:`PyTime_AsSecondsDouble` + to convert to a given unit. + + The supported range is at least around [-292.3 years; +292.3 years]. + Using the Unix epoch (January 1st, 1970) as reference, the supported date + range is at least around [1677-09-21; 2262-04-11]. + The exact limits are exposed as constants. .. c:var:: PyTime_t PyTime_MIN @@ -68,8 +76,8 @@ system time.) See :func:`time.time` for details important on this clock. -Other functions ---------------- +Conversion functions +-------------------- .. c:function:: double PyTime_AsSecondsDouble(PyTime_t t) @@ -77,3 +85,9 @@ Other functions The function cannot fail, but note that :c:expr:`double` has limited accuracy for large values. + +.. c:function:: int PyTime_AsNanoseconds(PyTime_t t, int64_t *result) + + Convert a timestamp to a number of nanoseconds as a 64-bit signed integer. + + Returns 0 on success, or -1 (with an exception set) on failure. From 069eb79e9242dc19731f0792302fc520ac531608 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 9 Feb 2024 13:22:48 +0100 Subject: [PATCH 06/20] Implement the fallible API and PyTime_AsNanoseconds --- Include/cpython/pytime.h | 19 ++++++-- Include/internal/pycore_time.h | 48 +++++++++++++++---- Modules/_randommodule.c | 4 +- Modules/_testcapi/time.c | 17 +++++-- Python/pytime.c | 85 ++++++++++++++++++++++------------ 5 files changed, 127 insertions(+), 46 deletions(-) diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h index 4179c15a4e6722..7fd1054fdf0b86 100644 --- a/Include/cpython/pytime.h +++ b/Include/cpython/pytime.h @@ -12,9 +12,22 @@ typedef int64_t PyTime_t; #define PyTime_MAX INT64_MAX PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t); -PyAPI_FUNC(PyTime_t) PyTime_Monotonic(void); -PyAPI_FUNC(PyTime_t) PyTime_PerfCounter(void); -PyAPI_FUNC(PyTime_t) PyTime_Time(void); +PyAPI_FUNC(int) PyTime_AsNanoseconds(PyTime_t t, int64_t *result); +PyAPI_FUNC(int) PyTime_Monotonic(PyTime_t *result); +PyAPI_FUNC(int) PyTime_PerfCounter(PyTime_t *result); +PyAPI_FUNC(int) PyTime_Time(PyTime_t *result); + +static inline int +_PyTime_AsNanoseconds_impl(PyTime_t t, int64_t *result) +{ + // Implementation detail: PyTime_t is already a int64_t in nanoseconds. + // This may change. We might e.g. extend PyTime_t to 128 bits, then + // this function can start raising OverflowError. + *result = t; + return 0; +} + +#define PyTime_AsNanoseconds _PyTime_AsNanoseconds_impl #ifdef __cplusplus } diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 6df69245be67e7..464b74358a0a74 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -173,9 +173,6 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t, PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round); -// Convert timestamp to a number of nanoseconds (10^-9 seconds). -extern _PyTime_t _PyTime_AsNanoseconds(_PyTime_t t); - #ifdef MS_WINDOWS // Convert timestamp to a number of 100 nanoseconds (10^-7 seconds). extern _PyTime_t _PyTime_As100Nanoseconds(_PyTime_t t, @@ -251,6 +248,17 @@ typedef struct { double resolution; } _Py_clock_info_t; +// Get the current time from the system clock. +// +// If the internal clock fails, silently ignore the error and return 0. +// On integer overflow, silently ignore the overflow and clamp the clock to +// [_PyTime_MIN; _PyTime_MAX]. +// +// Use _PyTime_GetSystemClockWithInfo or the public PyTime_Time() to check +// for failure. +// Export for '_random' shared extension. +PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void); + // Get the current time from the system clock. // On success, set *t and *info (if not NULL), and return 0. // On error, raise an exception and return -1. @@ -258,6 +266,20 @@ extern int _PyTime_GetSystemClockWithInfo( _PyTime_t *t, _Py_clock_info_t *info); +// 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 +// results of consecutive calls is valid. +// +// If the internal clock fails, silently ignore the error and return 0. +// On integer overflow, silently ignore the overflow and clamp the clock to +// [_PyTime_MIN; _PyTime_MAX]. +// +// Use _PyTime_GetMonotonicClockWithInfo or the public PyTime_Monotonic() +// to check for failure. +// Export for '_random' shared extension. +PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(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 @@ -282,6 +304,19 @@ 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); +// Get the performance counter: clock with the highest available resolution to +// measure a short duration. +// +// If the internal clock fails, silently ignore the error and return 0. +// On integer overflow, silently ignore the overflow and clamp the clock to +// [_PyTime_MIN; _PyTime_MAX]. +// +// Use _PyTime_GetPerfCounterWithInfo() or the public PyTime_PerfCounter +// to check for failure. +// Export for '_lsprof' shared extension. +PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void); + + // Get the performance counter: clock with the highest available resolution to // measure a short duration. // @@ -296,20 +331,17 @@ extern int _PyTime_GetPerfCounterWithInfo( #define _PyTime_MIN PyTime_MIN #define _PyTime_MAX PyTime_MAX #define _PyTime_AsSecondsDouble PyTime_AsSecondsDouble -#define _PyTime_GetMonotonicClock PyTime_Monotonic -#define _PyTime_GetPerfCounter PyTime_PerfCounter -#define _PyTime_GetSystemClock PyTime_Time // --- _PyDeadline ----------------------------------------------------------- // Create a deadline. -// Pseudo code: PyTime_Monotonic() + timeout. +// Pseudo code: _PyTime_GetMonotonicClock() + timeout. // Export for '_ssl' shared extension. PyAPI_FUNC(_PyTime_t) _PyDeadline_Init(_PyTime_t timeout); // Get remaining time from a deadline. -// Pseudo code: deadline - PyTime_Monotonic(). +// Pseudo code: deadline - _PyTime_GetMonotonicClock(). // Export for '_ssl' shared extension. PyAPI_FUNC(_PyTime_t) _PyDeadline_Get(_PyTime_t deadline); diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 80169de9ca93d7..5481ed9b348ed7 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -265,7 +265,7 @@ random_seed_time_pid(RandomObject *self) PyTime_t now; uint32_t key[5]; - now = PyTime_Time(); + now = _PyTime_GetSystemClock(); key[0] = (uint32_t)(now & 0xffffffffU); key[1] = (uint32_t)(now >> 32); @@ -277,7 +277,7 @@ random_seed_time_pid(RandomObject *self) key[2] = 0; #endif - now = PyTime_Monotonic(); + now = _PyTime_GetMonotonicClock(); key[3] = (uint32_t)(now & 0xffffffffU); key[4] = (uint32_t)(now >> 32); diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c index b0389402ae7aa4..4fbf7dd14ebb66 100644 --- a/Modules/_testcapi/time.c +++ b/Modules/_testcapi/time.c @@ -48,7 +48,10 @@ pytime_as_float(PyTime_t t) static PyObject* test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { - PyTime_t t = PyTime_Monotonic(); + PyTime_t t; + if (PyTime_Monotonic(&t) < 0) { + return NULL; + } return pytime_as_float(t); } @@ -56,7 +59,10 @@ test_pytime_monotonic(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) static PyObject* test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { - PyTime_t t = PyTime_PerfCounter(); + PyTime_t t; + if (PyTime_PerfCounter(&t) < 0) { + return NULL; + } return pytime_as_float(t); } @@ -64,7 +70,12 @@ test_pytime_perf_counter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) static PyObject* test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { - PyTime_t t = PyTime_Time(); + PyTime_t t; + if (PyTime_Time(&t) < 0) { + printf("ERR! %d\n", (int)t); + return NULL; + } + printf("... %d\n", (int)t); return pytime_as_float(t); } diff --git a/Python/pytime.c b/Python/pytime.c index e0bcecf0aa25d8..b111381caad328 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -784,13 +784,6 @@ pytime_divmod(const _PyTime_t t, const _PyTime_t k, } -_PyTime_t -_PyTime_AsNanoseconds(_PyTime_t t) -{ - return pytime_as_nanoseconds(t); -} - - #ifdef MS_WINDOWS _PyTime_t _PyTime_As100Nanoseconds(_PyTime_t t, _PyTime_round_t round) @@ -1037,16 +1030,16 @@ py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } -PyTime_t -PyTime_Time(void) +int +PyTime_Time(PyTime_t *result) { - PyTime_t t; - if (py_get_system_clock(&t, NULL, 0) < 0) { + if (py_get_system_clock(result, NULL, 1) < 0) { // If clock_gettime(CLOCK_REALTIME) or gettimeofday() fails: // silently ignore the failure and return 0. - t = 0; + *result = 0; + return -1; } - return t; + return 1; } @@ -1216,16 +1209,14 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } -PyTime_t -PyTime_Monotonic(void) +int +PyTime_Monotonic(PyTime_t *result) { - PyTime_t t; - if (py_get_monotonic_clock(&t, NULL, 0) < 0) { - // If mach_timebase_info(), clock_gettime() or gethrtime() fails: - // silently ignore the failure and return 0. - t = 0; + if (py_get_monotonic_clock(result, NULL, 1) < 0) { + *result = 0; + return -1; } - return t; + return 0; } @@ -1316,22 +1307,22 @@ _PyTime_GetPerfCounterWithInfo(_PyTime_t *t, _Py_clock_info_t *info) } -PyTime_t -PyTime_PerfCounter(void) +int +PyTime_PerfCounter(PyTime_t *result) { - PyTime_t t; int res; #ifdef MS_WINDOWS - res = py_get_win_perf_counter(&t, NULL, 0); + res = py_get_win_perf_counter(result, NULL, 1); #else - res = py_get_monotonic_clock(&t, NULL, 0); + res = py_get_monotonic_clock(result, NULL, 1); #endif if (res < 0) { // If py_win_perf_counter_frequency() or py_get_monotonic_clock() // fails: silently ignore the failure and return 0. - t = 0; + *result = 0; + return -1; } - return t; + return 0; } @@ -1405,7 +1396,11 @@ _PyTime_gmtime(time_t t, struct tm *tm) _PyTime_t _PyDeadline_Init(_PyTime_t timeout) { - _PyTime_t now = PyTime_Monotonic(); + _PyTime_t now; + if (PyTime_Monotonic(&now) < 0) { + PyErr_Clear(); + now = 0; + } return _PyTime_Add(now, timeout); } @@ -1413,6 +1408,36 @@ _PyDeadline_Init(_PyTime_t timeout) _PyTime_t _PyDeadline_Get(_PyTime_t deadline) { - _PyTime_t now = PyTime_Monotonic(); + _PyTime_t now; + if (PyTime_Monotonic(&now) < 0) { + PyErr_Clear(); + now = 0; + } return deadline - now; } + +// Internal wrappers that ignore all excetpions. +// (The exceptions are rare events; we don't care about performance +// if they're raised.) +#define NOEXC_WRAPPER(NAME, FUNC) \ + PyTime_t \ + NAME(void) { \ + PyTime_t result; \ + if (FUNC(&result) < 0) { \ + PyErr_Clear(); \ + return 0; \ + } \ + return result; \ + } + +NOEXC_WRAPPER(_PyTime_GetPerfCounter, PyTime_PerfCounter) +NOEXC_WRAPPER(_PyTime_GetMonotonicClock, PyTime_Monotonic) +NOEXC_WRAPPER(_PyTime_GetSystemClock, PyTime_Time) + +// Export PyTime_AsNanoseconds from libpython +#undef PyTime_AsNanoseconds +int +PyTime_AsNanoseconds(PyTime_t t, int64_t *result) +{ + return _PyTime_AsNanoseconds_impl(t, result); +} From 744f53d27fb96246f1fb617b720d73b62ecc7bfc Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 9 Feb 2024 13:50:55 +0100 Subject: [PATCH 07/20] Use PY_INT64_T in the public header --- Include/cpython/pytime.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h index 7fd1054fdf0b86..d71cafc4808c92 100644 --- a/Include/cpython/pytime.h +++ b/Include/cpython/pytime.h @@ -7,18 +7,18 @@ extern "C" { #endif -typedef int64_t PyTime_t; +typedef PY_INT64_T PyTime_t; #define PyTime_MIN INT64_MIN #define PyTime_MAX INT64_MAX PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t); -PyAPI_FUNC(int) PyTime_AsNanoseconds(PyTime_t t, int64_t *result); +PyAPI_FUNC(int) PyTime_AsNanoseconds(PyTime_t t, PY_INT64_T *result); PyAPI_FUNC(int) PyTime_Monotonic(PyTime_t *result); PyAPI_FUNC(int) PyTime_PerfCounter(PyTime_t *result); PyAPI_FUNC(int) PyTime_Time(PyTime_t *result); static inline int -_PyTime_AsNanoseconds_impl(PyTime_t t, int64_t *result) +_PyTime_AsNanoseconds_impl(PyTime_t t, PY_INT64_T *result) { // Implementation detail: PyTime_t is already a int64_t in nanoseconds. // This may change. We might e.g. extend PyTime_t to 128 bits, then From 38028a30ff5872d73f5b69a1d8c3d036c3fd338d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 9 Feb 2024 13:53:46 +0100 Subject: [PATCH 08/20] Adjust What's New & News --- Doc/whatsnew/3.13.rst | 10 +++++----- .../2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index dd39b56920c662..d68c189d14ac38 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1501,15 +1501,15 @@ New Features * Add :c:func:`Py_HashPointer` function to hash a pointer. (Contributed by Victor Stinner in :gh:`111545`.) -* Add clock C API: +* Add PyTime C API: * :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. + * :c:func:`PyTime_AsSecondsDouble`, :c:func:`PyTime_AsNanoseconds`, + :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and + :c:func:`PyTime_Time` functions. - (Contributed by Victor Stinner in :gh:`110850`.) + (Contributed by Victor Stinner and Petr Viktorin in :gh:`110850`.) Porting to Python 3.13 diff --git a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst index 50c6e9c4275b62..1d1523e1dc2532 100644 --- a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst +++ b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst @@ -2,8 +2,8 @@ Add PyTime C API: * :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. +* :c:func:`PyTime_AsSecondsDouble`, :c:func:`PyTime_AsNanoseconds`, + :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and + :c:func:`PyTime_Time` functions. Patch by Victor Stinner. From 74946d27513e04810901e6a974aa9da9ba1ce818 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 9 Feb 2024 14:14:31 +0100 Subject: [PATCH 09/20] Test PyTime_AsNanoseconds --- Lib/test/test_capi/test_time.py | 8 ++++++ Modules/_testcapi/time.c | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/Lib/test/test_capi/test_time.py b/Lib/test/test_capi/test_time.py index 10b7fbf2c372a3..124038ae6296d0 100644 --- a/Lib/test/test_capi/test_time.py +++ b/Lib/test/test_capi/test_time.py @@ -23,6 +23,11 @@ def check_clock(self, c_func, py_func): t2 = py_func() self.assertAlmostEqual(t1, t2, delta=CLOCK_RES) + def check_clock_ns(self, c_func, py_func): + t1 = c_func() + t2 = py_func()+2_000_000_000 + self.assertAlmostEqual(t1, t2, delta=CLOCK_RES * SEC_TO_NS) + def test_assecondsdouble(self): # Test PyTime_AsSecondsDouble() def ns_to_sec(ns): @@ -61,11 +66,14 @@ def ns_to_sec(ns): def test_monotonic(self): # Test PyTime_Monotonic() self.check_clock(_testcapi.PyTime_Monotonic, time.monotonic) + self.check_clock_ns(_testcapi.PyTime_Monotonic_ns, time.monotonic_ns) def test_perf_counter(self): # Test PyTime_PerfCounter() self.check_clock(_testcapi.PyTime_PerfCounter, time.perf_counter) + self.check_clock_ns(_testcapi.PyTime_PerfCounter_ns, time.perf_counter_ns) def test_time(self): # Test PyTime_time() self.check_clock(_testcapi.PyTime_Time, time.time) + self.check_clock_ns(_testcapi.PyTime_PerfCounter_ns, time.perf_counter_ns) diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c index 4fbf7dd14ebb66..784db5ee20b4c0 100644 --- a/Modules/_testcapi/time.c +++ b/Modules/_testcapi/time.c @@ -79,12 +79,61 @@ test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) return pytime_as_float(t); } +static PyObject* +pytime_as_ns(PyTime_t t) +{ + assert(sizeof(long long) >= sizeof(PyTime_t)); + long long result; + if (PyTime_AsNanoseconds(t, &result) < 0) { + return NULL; + } + return PyLong_FromLongLong(result); +} + + +static PyObject* +test_pytime_monotonic_ns(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t; + if (PyTime_Monotonic(&t) < 0) { + return NULL; + } + return pytime_as_ns(t); +} + + +static PyObject* +test_pytime_perf_counter_ns(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t; + if (PyTime_PerfCounter(&t) < 0) { + return NULL; + } + return pytime_as_ns(t); +} + + +static PyObject* +test_pytime_time_ns(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) +{ + PyTime_t t; + if (PyTime_Time(&t) < 0) { + printf("ERR! %d\n", (int)t); + return NULL; + } + printf("... %d\n", (int)t); + return pytime_as_ns(t); +} + static PyMethodDef test_methods[] = { {"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS}, {"PyTime_Monotonic", test_pytime_monotonic, METH_NOARGS}, {"PyTime_PerfCounter", test_pytime_perf_counter, METH_NOARGS}, {"PyTime_Time", test_pytime_time, METH_NOARGS}, + {"PyTime_Monotonic_ns", test_pytime_monotonic_ns, METH_NOARGS}, + {"PyTime_PerfCounter_ns", test_pytime_perf_counter_ns, METH_NOARGS}, + {"PyTime_Time_ns", test_pytime_time_ns, METH_NOARGS}, {NULL}, }; From 8f7992e5313eb00f41369c3fca24d8c0da3a205b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 9 Feb 2024 14:18:10 +0100 Subject: [PATCH 10/20] Add (indirect) tests for PyTime_AsNanoseconds --- Lib/test/test_capi/test_time.py | 34 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_capi/test_time.py b/Lib/test/test_capi/test_time.py index 124038ae6296d0..eb31929267e831 100644 --- a/Lib/test/test_capi/test_time.py +++ b/Lib/test/test_capi/test_time.py @@ -10,6 +10,7 @@ DAY_TO_SEC = (24 * 60 * 60) # Worst clock resolution: maximum delta between two clock reads. CLOCK_RES = 0.050 +CLOCK_RES_NS = int(CLOCK_RES * SEC_TO_NS) class CAPITest(unittest.TestCase): @@ -18,15 +19,15 @@ 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 check_clock_ns(self, c_func, py_func): - t1 = c_func() - t2 = py_func()+2_000_000_000 - self.assertAlmostEqual(t1, t2, delta=CLOCK_RES * SEC_TO_NS) + def check_clock(self, c_func, py_func, c_func_ns, py_func_ns): + t_c = c_func() + t_py = py_func() + t_c_ns = c_func_ns() + t_py_ns = py_func_ns() + self.assertAlmostEqual(t_c, t_py, delta=CLOCK_RES) + self.assertAlmostEqual(t_c_ns, t_py_ns, delta=CLOCK_RES_NS) + self.assertAlmostEqual(t_c * SEC_TO_NS, t_c_ns, delta=CLOCK_RES_NS) + self.assertAlmostEqual(t_py * SEC_TO_NS, t_py_ns, delta=CLOCK_RES_NS) def test_assecondsdouble(self): # Test PyTime_AsSecondsDouble() @@ -65,15 +66,18 @@ def ns_to_sec(ns): def test_monotonic(self): # Test PyTime_Monotonic() - self.check_clock(_testcapi.PyTime_Monotonic, time.monotonic) - self.check_clock_ns(_testcapi.PyTime_Monotonic_ns, time.monotonic_ns) + self.check_clock( + _testcapi.PyTime_Monotonic, time.monotonic, + _testcapi.PyTime_Monotonic_ns, time.monotonic_ns) def test_perf_counter(self): # Test PyTime_PerfCounter() - self.check_clock(_testcapi.PyTime_PerfCounter, time.perf_counter) - self.check_clock_ns(_testcapi.PyTime_PerfCounter_ns, time.perf_counter_ns) + self.check_clock( + _testcapi.PyTime_PerfCounter, time.perf_counter, + _testcapi.PyTime_PerfCounter_ns, time.perf_counter_ns) def test_time(self): # Test PyTime_time() - self.check_clock(_testcapi.PyTime_Time, time.time) - self.check_clock_ns(_testcapi.PyTime_PerfCounter_ns, time.perf_counter_ns) + self.check_clock( + _testcapi.PyTime_Time, time.time, + _testcapi.PyTime_Time_ns, time.time_ns) From ac94566de27c941df9dac9a435ab3adc068652b1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 12:32:49 +0100 Subject: [PATCH 11/20] Remove PyTime_AsNanoseconds --- Include/cpython/pytime.h | 13 --------- Lib/test/test_capi/test_time.py | 26 +++++------------ Modules/_testcapi/time.c | 49 --------------------------------- Python/pytime.c | 8 ------ 4 files changed, 7 insertions(+), 89 deletions(-) diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h index d71cafc4808c92..889daa81146a76 100644 --- a/Include/cpython/pytime.h +++ b/Include/cpython/pytime.h @@ -12,23 +12,10 @@ typedef PY_INT64_T PyTime_t; #define PyTime_MAX INT64_MAX PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t); -PyAPI_FUNC(int) PyTime_AsNanoseconds(PyTime_t t, PY_INT64_T *result); PyAPI_FUNC(int) PyTime_Monotonic(PyTime_t *result); PyAPI_FUNC(int) PyTime_PerfCounter(PyTime_t *result); PyAPI_FUNC(int) PyTime_Time(PyTime_t *result); -static inline int -_PyTime_AsNanoseconds_impl(PyTime_t t, PY_INT64_T *result) -{ - // Implementation detail: PyTime_t is already a int64_t in nanoseconds. - // This may change. We might e.g. extend PyTime_t to 128 bits, then - // this function can start raising OverflowError. - *result = t; - return 0; -} - -#define PyTime_AsNanoseconds _PyTime_AsNanoseconds_impl - #ifdef __cplusplus } #endif diff --git a/Lib/test/test_capi/test_time.py b/Lib/test/test_capi/test_time.py index eb31929267e831..10b7fbf2c372a3 100644 --- a/Lib/test/test_capi/test_time.py +++ b/Lib/test/test_capi/test_time.py @@ -10,7 +10,6 @@ DAY_TO_SEC = (24 * 60 * 60) # Worst clock resolution: maximum delta between two clock reads. CLOCK_RES = 0.050 -CLOCK_RES_NS = int(CLOCK_RES * SEC_TO_NS) class CAPITest(unittest.TestCase): @@ -19,15 +18,10 @@ 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, c_func_ns, py_func_ns): - t_c = c_func() - t_py = py_func() - t_c_ns = c_func_ns() - t_py_ns = py_func_ns() - self.assertAlmostEqual(t_c, t_py, delta=CLOCK_RES) - self.assertAlmostEqual(t_c_ns, t_py_ns, delta=CLOCK_RES_NS) - self.assertAlmostEqual(t_c * SEC_TO_NS, t_c_ns, delta=CLOCK_RES_NS) - self.assertAlmostEqual(t_py * SEC_TO_NS, t_py_ns, delta=CLOCK_RES_NS) + 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() @@ -66,18 +60,12 @@ def ns_to_sec(ns): def test_monotonic(self): # Test PyTime_Monotonic() - self.check_clock( - _testcapi.PyTime_Monotonic, time.monotonic, - _testcapi.PyTime_Monotonic_ns, time.monotonic_ns) + self.check_clock(_testcapi.PyTime_Monotonic, time.monotonic) def test_perf_counter(self): # Test PyTime_PerfCounter() - self.check_clock( - _testcapi.PyTime_PerfCounter, time.perf_counter, - _testcapi.PyTime_PerfCounter_ns, time.perf_counter_ns) + self.check_clock(_testcapi.PyTime_PerfCounter, time.perf_counter) def test_time(self): # Test PyTime_time() - self.check_clock( - _testcapi.PyTime_Time, time.time, - _testcapi.PyTime_Time_ns, time.time_ns) + self.check_clock(_testcapi.PyTime_Time, time.time) diff --git a/Modules/_testcapi/time.c b/Modules/_testcapi/time.c index 784db5ee20b4c0..4fbf7dd14ebb66 100644 --- a/Modules/_testcapi/time.c +++ b/Modules/_testcapi/time.c @@ -79,61 +79,12 @@ test_pytime_time(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) return pytime_as_float(t); } -static PyObject* -pytime_as_ns(PyTime_t t) -{ - assert(sizeof(long long) >= sizeof(PyTime_t)); - long long result; - if (PyTime_AsNanoseconds(t, &result) < 0) { - return NULL; - } - return PyLong_FromLongLong(result); -} - - -static PyObject* -test_pytime_monotonic_ns(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) -{ - PyTime_t t; - if (PyTime_Monotonic(&t) < 0) { - return NULL; - } - return pytime_as_ns(t); -} - - -static PyObject* -test_pytime_perf_counter_ns(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) -{ - PyTime_t t; - if (PyTime_PerfCounter(&t) < 0) { - return NULL; - } - return pytime_as_ns(t); -} - - -static PyObject* -test_pytime_time_ns(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) -{ - PyTime_t t; - if (PyTime_Time(&t) < 0) { - printf("ERR! %d\n", (int)t); - return NULL; - } - printf("... %d\n", (int)t); - return pytime_as_ns(t); -} - static PyMethodDef test_methods[] = { {"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS}, {"PyTime_Monotonic", test_pytime_monotonic, METH_NOARGS}, {"PyTime_PerfCounter", test_pytime_perf_counter, METH_NOARGS}, {"PyTime_Time", test_pytime_time, METH_NOARGS}, - {"PyTime_Monotonic_ns", test_pytime_monotonic_ns, METH_NOARGS}, - {"PyTime_PerfCounter_ns", test_pytime_perf_counter_ns, METH_NOARGS}, - {"PyTime_Time_ns", test_pytime_time_ns, METH_NOARGS}, {NULL}, }; diff --git a/Python/pytime.c b/Python/pytime.c index b111381caad328..cf1fb3281562fa 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -1433,11 +1433,3 @@ _PyDeadline_Get(_PyTime_t deadline) NOEXC_WRAPPER(_PyTime_GetPerfCounter, PyTime_PerfCounter) NOEXC_WRAPPER(_PyTime_GetMonotonicClock, PyTime_Monotonic) NOEXC_WRAPPER(_PyTime_GetSystemClock, PyTime_Time) - -// Export PyTime_AsNanoseconds from libpython -#undef PyTime_AsNanoseconds -int -PyTime_AsNanoseconds(PyTime_t t, int64_t *result) -{ - return _PyTime_AsNanoseconds_impl(t, result); -} From f4fc84935b3ab1951e15dbe575f5701fd6c851ed Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 12:36:53 +0100 Subject: [PATCH 12/20] Update docs to mention PyTime_t size and resolution, remove PyTime_AsNanoseconds. --- Doc/c-api/time.rst | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index ff7e4df43089b8..19316538efcc6f 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -5,7 +5,7 @@ PyTime C API .. versionadded:: 3.13 -The clock C API provides access to system clocks and time conversion functions. +The clock C API provides access to system clocks. It is similar to the Python :mod:`time` module. For C API related to the :mod:`datetime` module, see :ref:`datetimeobjects`. @@ -16,23 +16,15 @@ Types .. c:type:: PyTime_t - A timestamp or duration. + A timestamp or duration in nanoseconds, represented as a signed 64-bit + integer. The reference point for timestamps depends on the clock used. For example, :c:func:`PyTime_Time` returns timestamps relative to the UNIX epoch. - ``PyTime_t`` is a signed integral type. It is possible to add and - subtract such values directly. - Keep in mind that C overflow rules do apply. - - ``PyTime_t`` can represent time with nanosecond or better resolution. - The unit used is an internal implementation detail; use - :c:func:`PyTime_AsNanoseconds` or :c:func:`PyTime_AsSecondsDouble` - to convert to a given unit. - - The supported range is at least around [-292.3 years; +292.3 years]. + The supported range is around [-292.3 years; +292.3 years]. Using the Unix epoch (January 1st, 1970) as reference, the supported date - range is at least around [1677-09-21; 2262-04-11]. + range is around [1677-09-21; 2262-04-11]. The exact limits are exposed as constants. .. c:var:: PyTime_t PyTime_MIN @@ -85,9 +77,3 @@ Conversion functions The function cannot fail, but note that :c:expr:`double` has limited accuracy for large values. - -.. c:function:: int PyTime_AsNanoseconds(PyTime_t t, int64_t *result) - - Convert a timestamp to a number of nanoseconds as a 64-bit signed integer. - - Returns 0 on success, or -1 (with an exception set) on failure. From 3d43b73cd0d13523398d532073595aa25263ea96 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 12:38:05 +0100 Subject: [PATCH 13/20] Dedent docs for PyTime_MIN & MAX constants --- Doc/c-api/time.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index 19316538efcc6f..d356c4743774fa 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -25,15 +25,15 @@ Types The supported range is around [-292.3 years; +292.3 years]. Using the Unix epoch (January 1st, 1970) as reference, the supported date range is around [1677-09-21; 2262-04-11]. - The exact limits are exposed as constants. + The exact limits are exposed as constants: - .. c:var:: PyTime_t PyTime_MIN +.. c:var:: PyTime_t PyTime_MIN - Minimum value of :c:type:`PyTime_t`. + Minimum value of :c:type:`PyTime_t`. - .. c:var:: PyTime_t PyTime_MAX +.. c:var:: PyTime_t PyTime_MAX - Maximum value of :c:type:`PyTime_t`. + Maximum value of :c:type:`PyTime_t`. Clock Functions From ba1c30bbe8903e575d688ddf2401fb2161f3e656 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 12:43:19 +0100 Subject: [PATCH 14/20] Use int64_t rather than PY_INT64_T --- Include/cpython/pytime.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h index 889daa81146a76..d8244700d614ce 100644 --- a/Include/cpython/pytime.h +++ b/Include/cpython/pytime.h @@ -7,7 +7,7 @@ extern "C" { #endif -typedef PY_INT64_T PyTime_t; +typedef int64_t PyTime_t; #define PyTime_MIN INT64_MIN #define PyTime_MAX INT64_MAX From 9fb54307717b82066c24284644846b247c1a453f Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 13:23:10 +0100 Subject: [PATCH 15/20] Add back full implementations of infallible functions --- Python/pytime.c | 75 ++++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/Python/pytime.c b/Python/pytime.c index cf1fb3281562fa..a4246cf0a00e9b 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -1042,13 +1042,24 @@ PyTime_Time(PyTime_t *result) return 1; } - int _PyTime_GetSystemClockWithInfo(_PyTime_t *t, _Py_clock_info_t *info) { return py_get_system_clock(t, info, 1); } +_PyTime_t +_PyTime_GetSystemClock(void) +{ + _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; + } + return t; +} + #ifdef __APPLE__ static int @@ -1227,6 +1238,19 @@ _PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) } +_PyTime_t +_PyTime_GetMonotonicClock(void) +{ + _PyTime_t t; + if (py_get_monotonic_clock(&t, NULL, 0) < 0) { + // If mach_timebase_info(), clock_gettime() or gethrtime() fails: + // silently ignore the failure and return 0. + t = 0; + } + return t; +} + + #ifdef MS_WINDOWS static int py_win_perf_counter_frequency(_PyTimeFraction *base, int raise) @@ -1326,6 +1350,25 @@ PyTime_PerfCounter(PyTime_t *result) } +_PyTime_t +_PyTime_GetPerfCounter(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_win_perf_counter_frequency() or py_get_monotonic_clock() + // fails: silently ignore the failure and return 0. + t = 0; + } + return t; +} + + int _PyTime_localtime(time_t t, struct tm *tm) { @@ -1396,11 +1439,7 @@ _PyTime_gmtime(time_t t, struct tm *tm) _PyTime_t _PyDeadline_Init(_PyTime_t timeout) { - _PyTime_t now; - if (PyTime_Monotonic(&now) < 0) { - PyErr_Clear(); - now = 0; - } + _PyTime_t now = _PyTime_GetMonotonicClock(); return _PyTime_Add(now, timeout); } @@ -1408,28 +1447,6 @@ _PyDeadline_Init(_PyTime_t timeout) _PyTime_t _PyDeadline_Get(_PyTime_t deadline) { - _PyTime_t now; - if (PyTime_Monotonic(&now) < 0) { - PyErr_Clear(); - now = 0; - } + _PyTime_t now = _PyTime_GetMonotonicClock(); return deadline - now; } - -// Internal wrappers that ignore all excetpions. -// (The exceptions are rare events; we don't care about performance -// if they're raised.) -#define NOEXC_WRAPPER(NAME, FUNC) \ - PyTime_t \ - NAME(void) { \ - PyTime_t result; \ - if (FUNC(&result) < 0) { \ - PyErr_Clear(); \ - return 0; \ - } \ - return result; \ - } - -NOEXC_WRAPPER(_PyTime_GetPerfCounter, PyTime_PerfCounter) -NOEXC_WRAPPER(_PyTime_GetMonotonicClock, PyTime_Monotonic) -NOEXC_WRAPPER(_PyTime_GetSystemClock, PyTime_Time) From 47f5d07560d92e46125fa02d99305d799ca7c2ab Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 13:28:26 +0100 Subject: [PATCH 16/20] Add notes about the GIL --- Doc/c-api/time.rst | 3 +++ Python/pytime.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index d356c4743774fa..f21eba50ae8a3f 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -52,6 +52,9 @@ range. (On current systems, integer overflows are likely caused by misconfigured system time.) +As any other C API (unless otherwise specified), the functions must be called +with the :term:`GIL` held. + .. c:function:: int PyTime_Monotonic(PyTime_t *result) Read the monotonic clock. diff --git a/Python/pytime.c b/Python/pytime.c index a4246cf0a00e9b..7aa8ce7c6978cc 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -919,6 +919,7 @@ _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts) #endif +// N.B. If raise_exc=0, this may be called without the GIL. static int py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { @@ -1096,6 +1097,7 @@ py_mach_timebase_info(_PyTimeFraction *base, int raise) #endif +// N.B. If raise_exc=0, this may be called without the GIL. static int py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { @@ -1283,6 +1285,7 @@ py_win_perf_counter_frequency(_PyTimeFraction *base, int raise) } +// N.B. If raise_exc=0, this may be called without the GIL. static int py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { From 7fad97d79ac5078d07856ba5039b2c2288aa4797 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 13:30:33 +0100 Subject: [PATCH 17/20] Remove PyTime_AsNanoseconds also from What's New & NEWS --- Doc/whatsnew/3.13.rst | 2 +- .../next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 0611a4f68ecf40..191657061f7403 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1520,7 +1520,7 @@ New Features * :c:type:`PyTime_t` type. * :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants. - * :c:func:`PyTime_AsSecondsDouble`, :c:func:`PyTime_AsNanoseconds`, + * :c:func:`PyTime_AsSecondsDouble` :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and :c:func:`PyTime_Time` functions. diff --git a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst index 1d1523e1dc2532..998d4426dd53f9 100644 --- a/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst +++ b/Misc/NEWS.d/next/C API/2023-11-16-02-07-48.gh-issue-110850.DQGNfF.rst @@ -2,7 +2,7 @@ Add PyTime C API: * :c:type:`PyTime_t` type. * :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants. -* :c:func:`PyTime_AsSecondsDouble`, :c:func:`PyTime_AsNanoseconds`, +* :c:func:`PyTime_AsSecondsDouble`, :c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and :c:func:`PyTime_Time` functions. From 960c779e92e705b817a768c6ddad6ceee89b1e9f Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 13:40:37 +0100 Subject: [PATCH 18/20] Note that PyTime_t is fixed now. --- Doc/c-api/time.rst | 3 ++- Include/internal/pycore_time.h | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index f21eba50ae8a3f..dcbdeb04ebf13f 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -44,7 +44,8 @@ set to the value of a particular clock. Details of each clock are given in the documentation of the corresponding Python function. -The functions return 0 on success, or -1 (with an exception set) on failure. +The functions return ``0`` on success, or ``-1`` (with an exception set) +on failure. On integer overflow, they set the :c:data:`PyExc_OverflowError` exception and set :c:expr:`*result` to the value clamped to the ``[PyTime_MIN; PyTime_MAX]`` diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 464b74358a0a74..1aad6ccea69ae3 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -14,6 +14,11 @@ // * timeval structure, 1 microsecond (10^-6 seconds). // * timespec structure, 1 nanosecond (10^-9 seconds). // +// Note that PyTime_t is now specified as int64_t, in nanoseconds. +// (If we need to change this, we'll need new public API with new names.) +// Previously, PyTime_t was configurable (in theory); some comments and code +// might still allude to that. +// // Integer overflows are detected and raise OverflowError. Conversion to a // resolution larger than 1 nanosecond is rounded correctly with the requested // rounding mode. Available rounding modes: From 988340f1cdf80982aa6f629e02f7317b6bb4e6f4 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 13:44:04 +0100 Subject: [PATCH 19/20] Reorder the functions to reduce the diff --- Python/pytime.c | 79 +++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/Python/pytime.c b/Python/pytime.c index 7aa8ce7c6978cc..fb0ed85c541e68 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -1031,6 +1031,19 @@ py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } +_PyTime_t +_PyTime_GetSystemClock(void) +{ + _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; + } + return t; +} + + int PyTime_Time(PyTime_t *result) { @@ -1049,18 +1062,6 @@ _PyTime_GetSystemClockWithInfo(_PyTime_t *t, _Py_clock_info_t *info) return py_get_system_clock(t, info, 1); } -_PyTime_t -_PyTime_GetSystemClock(void) -{ - _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; - } - return t; -} - #ifdef __APPLE__ static int @@ -1222,6 +1223,19 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } +_PyTime_t +_PyTime_GetMonotonicClock(void) +{ + _PyTime_t t; + if (py_get_monotonic_clock(&t, NULL, 0) < 0) { + // If mach_timebase_info(), clock_gettime() or gethrtime() fails: + // silently ignore the failure and return 0. + t = 0; + } + return t; +} + + int PyTime_Monotonic(PyTime_t *result) { @@ -1240,19 +1254,6 @@ _PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) } -_PyTime_t -_PyTime_GetMonotonicClock(void) -{ - _PyTime_t t; - if (py_get_monotonic_clock(&t, NULL, 0) < 0) { - // If mach_timebase_info(), clock_gettime() or gethrtime() fails: - // silently ignore the failure and return 0. - t = 0; - } - return t; -} - - #ifdef MS_WINDOWS static int py_win_perf_counter_frequency(_PyTimeFraction *base, int raise) @@ -1334,41 +1335,41 @@ _PyTime_GetPerfCounterWithInfo(_PyTime_t *t, _Py_clock_info_t *info) } -int -PyTime_PerfCounter(PyTime_t *result) +_PyTime_t +_PyTime_GetPerfCounter(void) { + _PyTime_t t; int res; #ifdef MS_WINDOWS - res = py_get_win_perf_counter(result, NULL, 1); + res = py_get_win_perf_counter(&t, NULL, 0); #else - res = py_get_monotonic_clock(result, NULL, 1); + res = py_get_monotonic_clock(&t, NULL, 0); #endif if (res < 0) { // If py_win_perf_counter_frequency() or py_get_monotonic_clock() // fails: silently ignore the failure and return 0. - *result = 0; - return -1; + t = 0; } - return 0; + return t; } -_PyTime_t -_PyTime_GetPerfCounter(void) +int +PyTime_PerfCounter(PyTime_t *result) { - _PyTime_t t; int res; #ifdef MS_WINDOWS - res = py_get_win_perf_counter(&t, NULL, 0); + res = py_get_win_perf_counter(result, NULL, 1); #else - res = py_get_monotonic_clock(&t, NULL, 0); + res = py_get_monotonic_clock(result, NULL, 1); #endif if (res < 0) { // If py_win_perf_counter_frequency() or py_get_monotonic_clock() // fails: silently ignore the failure and return 0. - t = 0; + *result = 0; + return -1; } - return t; + return 0; } From fbd801bc7aea43ff0ecc0f01d9beb50d35d912c2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Feb 2024 13:51:26 +0100 Subject: [PATCH 20/20] Fix ReST warning --- Doc/c-api/time.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/time.rst b/Doc/c-api/time.rst index dcbdeb04ebf13f..7791cdb1781055 100644 --- a/Doc/c-api/time.rst +++ b/Doc/c-api/time.rst @@ -48,7 +48,7 @@ The functions return ``0`` on success, or ``-1`` (with an exception set) on failure. On integer overflow, they set the :c:data:`PyExc_OverflowError` exception and -set :c:expr:`*result` to the value clamped to the ``[PyTime_MIN; PyTime_MAX]`` +set ``*result`` to the value clamped to the ``[PyTime_MIN; PyTime_MAX]`` range. (On current systems, integer overflows are likely caused by misconfigured system time.)