Skip to content

Commit 4c2cd24

Browse files
committed
pythongh-112567: Add _PyTimeFraction C API
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Denominator (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _Py_GetTicksPerSecond() * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * Remove _PyTime_Init(). * Remove _PyRuntimeState.time member. * os.times() now calls _Py_GetTicksPerSecond(). * Rename _PyTime_GetClockWithInfo() to py_clock().
1 parent 6d5e0dc commit 4c2cd24

File tree

9 files changed

+214
-173
lines changed

9 files changed

+214
-173
lines changed

Include/internal/pycore_fileutils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,10 @@ PyAPI_FUNC(char*) _Py_UniversalNewlineFgetsWithSize(char *, int, FILE*, PyObject
320320

321321
extern int _PyFile_Flush(PyObject *);
322322

323+
#ifndef MS_WINDOWS
324+
extern int _Py_GetTicksPerSecond(long *ticks_per_second);
325+
#endif
326+
323327
#ifdef __cplusplus
324328
}
325329
#endif

Include/internal/pycore_pylifecycle.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ extern void _PySys_FiniTypes(PyInterpreterState *interp);
4040
extern int _PyBuiltins_AddExceptions(PyObject * bltinmod);
4141
extern PyStatus _Py_HashRandomization_Init(const PyConfig *);
4242

43-
extern PyStatus _PyTime_Init(void);
4443
extern PyStatus _PyGC_Init(PyInterpreterState *interp);
4544
extern PyStatus _PyAtExit_Init(PyInterpreterState *interp);
4645
extern int _Py_Deepfreeze_Init(void);

Include/internal/pycore_runtime.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ extern "C" {
2121
#include "pycore_pymem.h" // struct _pymem_allocators
2222
#include "pycore_pythread.h" // struct _pythread_runtime_state
2323
#include "pycore_signal.h" // struct _signals_runtime_state
24-
#include "pycore_time.h" // struct _time_runtime_state
2524
#include "pycore_tracemalloc.h" // struct _tracemalloc_runtime_state
2625
#include "pycore_typeobject.h" // struct _types_runtime_state
2726
#include "pycore_unicodeobject.h" // struct _Py_unicode_runtime_state
@@ -205,7 +204,6 @@ typedef struct pyruntimestate {
205204
struct _pymem_allocators allocators;
206205
struct _obmalloc_global_state obmalloc;
207206
struct pyhash_runtime_state pyhash_state;
208-
struct _time_runtime_state time;
209207
struct _pythread_runtime_state threads;
210208
struct _signals_runtime_state signals;
211209

Include/internal/pycore_time.h

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,6 @@ extern "C" {
5252
#endif
5353

5454

55-
struct _time_runtime_state {
56-
#ifdef HAVE_TIMES
57-
int ticks_per_second_initialized;
58-
long ticks_per_second;
59-
#else
60-
int _not_used;
61-
#endif
62-
};
63-
64-
6555
#ifdef __clang__
6656
struct timeval;
6757
#endif
@@ -263,13 +253,6 @@ PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts);
263253
// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
264254
extern _PyTime_t _PyTime_Add(_PyTime_t t1, _PyTime_t t2);
265255

266-
// Compute ticks * mul / div.
267-
// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
268-
// The caller must ensure that ((div - 1) * mul) cannot overflow.
269-
extern _PyTime_t _PyTime_MulDiv(_PyTime_t ticks,
270-
_PyTime_t mul,
271-
_PyTime_t div);
272-
273256
// Structure used by time.get_clock_info()
274257
typedef struct {
275258
const char *implementation;
@@ -365,6 +348,32 @@ PyAPI_FUNC(_PyTime_t) _PyDeadline_Init(_PyTime_t timeout);
365348
PyAPI_FUNC(_PyTime_t) _PyDeadline_Get(_PyTime_t deadline);
366349

367350

351+
// --- _PyTimeFraction -------------------------------------------------------
352+
353+
typedef struct {
354+
_PyTime_t numer;
355+
_PyTime_t denom;
356+
} _PyTimeFraction;
357+
358+
// Set a fraction.
359+
// Return 0 on success.
360+
// Return -1 if the fraction is invalid.
361+
extern int _PyTimeFraction_Set(
362+
_PyTimeFraction *frac,
363+
_PyTime_t numer,
364+
_PyTime_t denom);
365+
366+
// Compute ticks * frac.numer / frac.denom.
367+
// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
368+
extern _PyTime_t _PyTimeFraction_Mul(
369+
_PyTime_t ticks,
370+
const _PyTimeFraction *frac);
371+
372+
// Compute a clock resolution: frac.numer / frac.denom / 1e9.
373+
extern double _PyTimeFraction_Resolution(
374+
const _PyTimeFraction *frac);
375+
376+
368377
#ifdef __cplusplus
369378
}
370379
#endif

Modules/posixmodule.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10086,22 +10086,29 @@ os_times_impl(PyObject *module)
1008610086
}
1008710087
#else /* MS_WINDOWS */
1008810088
{
10089+
long ticks_per_second = 0;
10090+
if (ticks_per_second == 0) {
10091+
if (_Py_GetTicksPerSecond(&ticks_per_second) < 0) {
10092+
PyErr_SetString(PyExc_RuntimeError,
10093+
"cannot read ticks_per_second");
10094+
return NULL;
10095+
}
10096+
}
10097+
1008910098
struct tms t;
1009010099
clock_t c;
1009110100
errno = 0;
1009210101
c = times(&t);
1009310102
if (c == (clock_t) -1) {
1009410103
return posix_error();
1009510104
}
10096-
assert(_PyRuntime.time.ticks_per_second_initialized);
10097-
#define ticks_per_second _PyRuntime.time.ticks_per_second
10105+
1009810106
return build_times_result(module,
1009910107
(double)t.tms_utime / ticks_per_second,
1010010108
(double)t.tms_stime / ticks_per_second,
1010110109
(double)t.tms_cutime / ticks_per_second,
1010210110
(double)t.tms_cstime / ticks_per_second,
1010310111
(double)c / ticks_per_second);
10104-
#undef ticks_per_second
1010510112
}
1010610113
#endif /* MS_WINDOWS */
1010710114
#endif /* HAVE_TIMES */

Modules/timemodule.c

Lines changed: 60 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -69,56 +69,6 @@ module time
6969
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=a668a08771581f36]*/
7070

7171

72-
#if defined(HAVE_TIMES) || defined(HAVE_CLOCK)
73-
static int
74-
check_ticks_per_second(long tps, const char *context)
75-
{
76-
/* Effectively, check that _PyTime_MulDiv(t, SEC_TO_NS, ticks_per_second)
77-
cannot overflow. */
78-
if (tps >= 0 && (_PyTime_t)tps > _PyTime_MAX / SEC_TO_NS) {
79-
PyErr_Format(PyExc_OverflowError, "%s is too large", context);
80-
return -1;
81-
}
82-
return 0;
83-
}
84-
#endif /* HAVE_TIMES || HAVE_CLOCK */
85-
86-
#ifdef HAVE_TIMES
87-
88-
# define ticks_per_second _PyRuntime.time.ticks_per_second
89-
90-
static void
91-
ensure_ticks_per_second(void)
92-
{
93-
if (_PyRuntime.time.ticks_per_second_initialized) {
94-
return;
95-
}
96-
_PyRuntime.time.ticks_per_second_initialized = 1;
97-
# if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK)
98-
ticks_per_second = sysconf(_SC_CLK_TCK);
99-
if (ticks_per_second < 1) {
100-
ticks_per_second = -1;
101-
}
102-
# elif defined(HZ)
103-
ticks_per_second = HZ;
104-
# else
105-
ticks_per_second = 60; /* magic fallback value; may be bogus */
106-
# endif
107-
}
108-
109-
#endif /* HAVE_TIMES */
110-
111-
112-
PyStatus
113-
_PyTime_Init(void)
114-
{
115-
#ifdef HAVE_TIMES
116-
ensure_ticks_per_second();
117-
#endif
118-
return PyStatus_Ok();
119-
}
120-
121-
12272
/* Forward declarations */
12373
static int pysleep(_PyTime_t timeout);
12474

@@ -195,15 +145,20 @@ Return the current time in nanoseconds since the Epoch.");
195145
#endif
196146

197147
static int
198-
_PyTime_GetClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
148+
py_clock(_PyTime_t *tp, _Py_clock_info_t *info)
199149
{
200-
if (check_ticks_per_second(CLOCKS_PER_SEC, "CLOCKS_PER_SEC") < 0) {
201-
return -1;
150+
static _PyTimeFraction base = {0, 0};
151+
if (base.denom == 0) {
152+
_PyTime_t frequency = CLOCKS_PER_SEC;
153+
if (_PyTimeFraction_Set(&base, SEC_TO_NS, frequency) < 0) {
154+
PyErr_Format(PyExc_OverflowError, "CLOCKS_PER_SEC is too large");
155+
return -1;
156+
}
202157
}
203158

204159
if (info) {
205160
info->implementation = "clock()";
206-
info->resolution = 1.0 / (double)CLOCKS_PER_SEC;
161+
info->resolution = _PyTimeFraction_Resolution(&base);
207162
info->monotonic = 1;
208163
info->adjustable = 0;
209164
}
@@ -215,7 +170,7 @@ _PyTime_GetClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
215170
"or its value cannot be represented");
216171
return -1;
217172
}
218-
_PyTime_t ns = _PyTime_MulDiv(ticks, SEC_TO_NS, (_PyTime_t)CLOCKS_PER_SEC);
173+
_PyTime_t ns = _PyTimeFraction_Mul(ticks, &base);
219174
*tp = _PyTime_FromNanoseconds(ns);
220175
return 0;
221176
}
@@ -1277,6 +1232,47 @@ PyDoc_STRVAR(perf_counter_ns_doc,
12771232
\n\
12781233
Performance counter for benchmarking as nanoseconds.");
12791234

1235+
1236+
#ifdef HAVE_TIMES
1237+
static int
1238+
process_time_times(_PyTime_t *tp, _Py_clock_info_t *info)
1239+
{
1240+
static _PyTimeFraction base = {0, 0};
1241+
if (base.denom == 0) {
1242+
long ticks_per_second;
1243+
if (_Py_GetTicksPerSecond(&ticks_per_second) < 0) {
1244+
PyErr_SetString(PyExc_RuntimeError,
1245+
"cannot read ticks_per_second");
1246+
return -1;
1247+
}
1248+
1249+
if (_PyTimeFraction_Set(&base, SEC_TO_NS, ticks_per_second) < 0) {
1250+
PyErr_Format(PyExc_OverflowError, "ticks_per_second is too large");
1251+
return -1;
1252+
}
1253+
}
1254+
1255+
struct tms process;
1256+
if (times(&process) == (clock_t)-1) {
1257+
return 0;
1258+
}
1259+
1260+
if (info) {
1261+
info->implementation = "times()";
1262+
info->resolution = _PyTimeFraction_Resolution(&base);
1263+
info->monotonic = 1;
1264+
info->adjustable = 0;
1265+
}
1266+
1267+
_PyTime_t ns;
1268+
ns = _PyTimeFraction_Mul(process.tms_utime, &base);
1269+
ns += _PyTimeFraction_Mul(process.tms_stime, &base);
1270+
*tp = _PyTime_FromNanoseconds(ns);
1271+
return 1;
1272+
}
1273+
#endif
1274+
1275+
12801276
static int
12811277
_PyTime_GetProcessTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
12821278
{
@@ -1381,33 +1377,18 @@ _PyTime_GetProcessTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
13811377

13821378
/* times() */
13831379
#ifdef HAVE_TIMES
1384-
struct tms t;
1385-
1386-
if (times(&t) != (clock_t)-1) {
1387-
assert(_PyRuntime.time.ticks_per_second_initialized);
1388-
if (check_ticks_per_second(ticks_per_second, "_SC_CLK_TCK") < 0) {
1389-
return -1;
1390-
}
1391-
if (ticks_per_second != -1) {
1392-
if (info) {
1393-
info->implementation = "times()";
1394-
info->monotonic = 1;
1395-
info->adjustable = 0;
1396-
info->resolution = 1.0 / (double)ticks_per_second;
1397-
}
1398-
1399-
_PyTime_t ns;
1400-
ns = _PyTime_MulDiv(t.tms_utime, SEC_TO_NS, ticks_per_second);
1401-
ns += _PyTime_MulDiv(t.tms_stime, SEC_TO_NS, ticks_per_second);
1402-
*tp = _PyTime_FromNanoseconds(ns);
1403-
return 0;
1404-
}
1380+
int res = process_time_times(tp, info);
1381+
if (res < 0) {
1382+
return -1;
1383+
}
1384+
if (res == 1) {
1385+
return 0;
14051386
}
1387+
// times() failed, ignore failure
14061388
#endif
14071389

1408-
/* clock */
1409-
/* Currently, Python 3 requires clock() to build: see issue #22624 */
1410-
return _PyTime_GetClockWithInfo(tp, info);
1390+
/* clock(). Python 3 requires clock() to build (see gh-66814) */
1391+
return py_clock(tp, info);
14111392
#endif
14121393
}
14131394

Python/fileutils.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2943,3 +2943,24 @@ _Py_closerange(int first, int last)
29432943
#endif /* USE_FDWALK */
29442944
_Py_END_SUPPRESS_IPH
29452945
}
2946+
2947+
2948+
#ifndef MS_WINDOWS
2949+
int
2950+
_Py_GetTicksPerSecond(long *ticks_per_second)
2951+
{
2952+
#if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK)
2953+
long value = sysconf(_SC_CLK_TCK);
2954+
if (value < 1) {
2955+
return -1;
2956+
}
2957+
*ticks_per_second = value;
2958+
# elif defined(HZ)
2959+
*ticks_per_second = HZ;
2960+
# else
2961+
// Magic fallback value; may be bogus
2962+
*ticks_per_second = 60;
2963+
# endif
2964+
return 0;
2965+
}
2966+
#endif

Python/pylifecycle.c

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -528,11 +528,6 @@ pycore_init_runtime(_PyRuntimeState *runtime,
528528
return status;
529529
}
530530

531-
status = _PyTime_Init();
532-
if (_PyStatus_EXCEPTION(status)) {
533-
return status;
534-
}
535-
536531
status = _PyImport_Init();
537532
if (_PyStatus_EXCEPTION(status)) {
538533
return status;

0 commit comments

Comments
 (0)