Skip to content

bpo-41710: Add _PyTime_AsTimespec_clamp() #28629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions Include/cpython/pytime.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern "C" {
typedef int64_t _PyTime_t;
#define _PyTime_MIN INT64_MIN
#define _PyTime_MAX INT64_MAX
#define _SIZEOF_PYTIME_T 8

typedef enum {
/* Round towards minus infinity (-inf).
Expand Down Expand Up @@ -136,8 +137,9 @@ PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
struct timeval *tv,
_PyTime_round_t round);

/* Similar to _PyTime_AsTimeval(), but don't raise an exception on error. */
PyAPI_FUNC(int) _PyTime_AsTimeval_noraise(_PyTime_t t,
/* Similar to _PyTime_AsTimeval() but don't raise an exception on overflow.
On overflow, clamp tv_sec to _PyTime_t min/max. */
PyAPI_FUNC(void) _PyTime_AsTimeval_clamp(_PyTime_t t,
struct timeval *tv,
_PyTime_round_t round);

Expand All @@ -162,6 +164,10 @@ PyAPI_FUNC(int) _PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts);
tv_nsec is always positive.
Raise an exception and return -1 on error, return 0 on success. */
PyAPI_FUNC(int) _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts);

/* Similar to _PyTime_AsTimespec() but don't raise an exception on overflow.
On overflow, clamp tv_sec to _PyTime_t min/max. */
PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts);
#endif

/* Compute ticks * mul / div.
Expand All @@ -181,7 +187,7 @@ typedef struct {
/* 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 truncated the clock to
On integer overflow, silently ignore the overflow and clamp the clock to
_PyTime_MIN or _PyTime_MAX.

Use _PyTime_GetSystemClockWithInfo() to check for failure. */
Expand All @@ -201,7 +207,7 @@ PyAPI_FUNC(int) _PyTime_GetSystemClockWithInfo(
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 truncated the clock to
On integer overflow, silently ignore the overflow and clamp the clock to
_PyTime_MIN or _PyTime_MAX.

Use _PyTime_GetMonotonicClockWithInfo() to check for failure. */
Expand Down Expand Up @@ -232,7 +238,7 @@ PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm);
measure a short duration.

If the internal clock fails, silently ignore the error and return 0.
On integer overflow, silently ignore the overflow and truncated the clock to
On integer overflow, silently ignore the overflow and clamp the clock to
_PyTime_MIN or _PyTime_MAX.

Use _PyTime_GetPerfCounterWithInfo() to check for failure. */
Expand Down
49 changes: 48 additions & 1 deletion Lib/test/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class _PyTime(enum.IntEnum):
# Round away from zero
ROUND_UP = 3

# _PyTime_t is int64_t
_PyTime_MIN = -2 ** 63
_PyTime_MAX = 2 ** 63 - 1

# Rounding modes supported by PyTime
ROUNDING_MODES = (
# (PyTime rounding method, decimal rounding method)
Expand Down Expand Up @@ -960,6 +964,49 @@ def timespec_converter(ns):
NS_TO_SEC,
value_filter=self.time_t_filter)

@unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimeval_clamp'),
'need _testcapi.PyTime_AsTimeval_clamp')
def test_AsTimeval_clamp(self):
from _testcapi import PyTime_AsTimeval_clamp

if sys.platform == 'win32':
from _testcapi import LONG_MIN, LONG_MAX
tv_sec_max = LONG_MAX
tv_sec_min = LONG_MIN
else:
tv_sec_max = self.time_t_max
tv_sec_min = self.time_t_min

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
us = self.decimal_round(decimal.Decimal(t) / US_TO_NS)
tv_sec, tv_usec = divmod(us, SEC_TO_US)
if tv_sec_max < tv_sec:
tv_sec = tv_sec_max
tv_usec = 0
elif tv_sec < tv_sec_min:
tv_sec = tv_sec_min
tv_usec = 0
self.assertEqual(ts, (tv_sec, tv_usec))

@unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimespec_clamp'),
'need _testcapi.PyTime_AsTimespec_clamp')
def test_AsTimespec_clamp(self):
from _testcapi import PyTime_AsTimespec_clamp

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:
tv_sec = self.time_t_max
tv_nsec = 0
elif tv_sec < self.time_t_min:
tv_sec = self.time_t_min
tv_nsec = 0
self.assertEqual(ts, (tv_sec, tv_nsec))

def test_AsMilliseconds(self):
from _testcapi import PyTime_AsMilliseconds

Expand Down Expand Up @@ -1062,7 +1109,7 @@ def test_clock_functions(self):
clock_names = [
"CLOCK_MONOTONIC", "clock_gettime", "clock_gettime_ns", "clock_settime",
"clock_settime_ns", "clock_getres"]

if mac_ver >= (10, 12):
for name in clock_names:
self.assertTrue(hasattr(time, name), f"time.{name} is not available")
Expand Down
2 changes: 1 addition & 1 deletion Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2264,7 +2264,7 @@ PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout)
if (!_PyIsSelectable_fd(s->sock_fd))
return SOCKET_TOO_LARGE_FOR_SELECT;

_PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING);
_PyTime_AsTimeval_clamp(timeout, &tv, _PyTime_ROUND_CEILING);

FD_ZERO(&fds);
FD_SET(s->sock_fd, &fds);
Expand Down
45 changes: 44 additions & 1 deletion Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4687,7 +4687,32 @@ test_PyTime_AsTimeval(PyObject *self, PyObject *args)
if (seconds == NULL) {
return NULL;
}
return Py_BuildValue("Nl", seconds, tv.tv_usec);
return Py_BuildValue("Nl", seconds, (long)tv.tv_usec);
}

static PyObject *
test_PyTime_AsTimeval_clamp(PyObject *self, PyObject *args)
{
PyObject *obj;
int round;
if (!PyArg_ParseTuple(args, "Oi", &obj, &round)) {
return NULL;
}
if (check_time_rounding(round) < 0) {
return NULL;
}
_PyTime_t t;
if (_PyTime_FromNanosecondsObject(&t, obj) < 0) {
return NULL;
}
struct timeval tv;
_PyTime_AsTimeval_clamp(t, &tv, round);

PyObject *seconds = PyLong_FromLongLong(tv.tv_sec);
if (seconds == NULL) {
return NULL;
}
return Py_BuildValue("Nl", seconds, (long)tv.tv_usec);
}

#ifdef HAVE_CLOCK_GETTIME
Expand All @@ -4708,6 +4733,22 @@ test_PyTime_AsTimespec(PyObject *self, PyObject *args)
}
return Py_BuildValue("Nl", _PyLong_FromTime_t(ts.tv_sec), ts.tv_nsec);
}

static PyObject *
test_PyTime_AsTimespec_clamp(PyObject *self, PyObject *args)
{
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj)) {
return NULL;
}
_PyTime_t t;
if (_PyTime_FromNanosecondsObject(&t, obj) < 0) {
return NULL;
}
struct timespec ts;
_PyTime_AsTimespec_clamp(t, &ts);
return Py_BuildValue("Nl", _PyLong_FromTime_t(ts.tv_sec), ts.tv_nsec);
}
#endif

static PyObject *
Expand Down Expand Up @@ -5872,8 +5913,10 @@ static PyMethodDef TestMethods[] = {
{"PyTime_FromSecondsObject", test_pytime_fromsecondsobject, METH_VARARGS},
{"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
{"PyTime_AsTimeval", test_PyTime_AsTimeval, METH_VARARGS},
{"PyTime_AsTimeval_clamp", test_PyTime_AsTimeval_clamp, METH_VARARGS},
#ifdef HAVE_CLOCK_GETTIME
{"PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS},
{"PyTime_AsTimespec_clamp", test_PyTime_AsTimespec_clamp, METH_VARARGS},
#endif
{"PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS},
{"PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS},
Expand Down
2 changes: 1 addition & 1 deletion Modules/selectmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist,
n = 0;
break;
}
_PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING);
_PyTime_AsTimeval_clamp(timeout, &tv, _PyTime_ROUND_CEILING);
/* retry select() with the recomputed timeout */
}
} while (1);
Expand Down
2 changes: 1 addition & 1 deletion Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ internal_select(PySocketSockObject *s, int writing, _PyTime_t interval,
Py_END_ALLOW_THREADS;
#else
if (interval >= 0) {
_PyTime_AsTimeval_noraise(interval, &tv, _PyTime_ROUND_CEILING);
_PyTime_AsTimeval_clamp(interval, &tv, _PyTime_ROUND_CEILING);
tvp = &tv;
}
else
Expand Down
Loading