Issue #20320: select.select() and select.kqueue.control() now round the timeout

aways from zero, instead of rounding towards zero.

It should make test_asyncio more reliable, especially test_timeout_rounding() test.
This commit is contained in:
Victor Stinner 2014-02-17 00:02:43 +01:00
parent 23f628de4a
commit 3c1b379ebd
10 changed files with 179 additions and 62 deletions

View file

@ -53,10 +53,19 @@ do { \
(tv_end.tv_usec - tv_start.tv_usec) * 0.000001)
#ifndef Py_LIMITED_API
typedef enum {
/* Round towards zero. */
_PyTime_ROUND_DOWN=0,
/* Round away from zero. */
_PyTime_ROUND_UP
} _PyTime_round_t;
/* Convert a number of seconds, int or float, to time_t. */
PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
PyObject *obj,
time_t *sec);
time_t *sec,
_PyTime_round_t);
/* Convert a time_t to a PyLong. */
PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
@ -72,7 +81,8 @@ PyAPI_FUNC(time_t) _PyLong_AsTime_t(
PyAPI_FUNC(int) _PyTime_ObjectToTimeval(
PyObject *obj,
time_t *sec,
long *usec);
long *usec,
_PyTime_round_t);
/* Convert a number of seconds, int or float, to a timespec structure.
nsec is in the range [0; 999999999] and rounded towards zero.
@ -80,7 +90,8 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimeval(
PyAPI_FUNC(int) _PyTime_ObjectToTimespec(
PyObject *obj,
time_t *sec,
long *nsec);
long *nsec,
_PyTime_round_t);
#endif
/* Dummy to force linking. */

View file

@ -14,6 +14,8 @@
SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4
TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1
TIME_MINYEAR = -TIME_MAXYEAR - 1
_PyTime_ROUND_DOWN = 0
_PyTime_ROUND_UP = 1
class TimeTestCase(unittest.TestCase):
@ -585,58 +587,116 @@ def setUp(self):
@support.cpython_only
def test_time_t(self):
from _testcapi import pytime_object_to_time_t
for obj, time_t in (
(0, 0),
(-1, -1),
(-1.0, -1),
(-1.9, -1),
(1.0, 1),
(1.9, 1),
for obj, time_t, rnd in (
# Round towards zero
(0, 0, _PyTime_ROUND_DOWN),
(-1, -1, _PyTime_ROUND_DOWN),
(-1.0, -1, _PyTime_ROUND_DOWN),
(-1.9, -1, _PyTime_ROUND_DOWN),
(1.0, 1, _PyTime_ROUND_DOWN),
(1.9, 1, _PyTime_ROUND_DOWN),
# Round away from zero
(0, 0, _PyTime_ROUND_UP),
(-1, -1, _PyTime_ROUND_UP),
(-1.0, -1, _PyTime_ROUND_UP),
(-1.9, -2, _PyTime_ROUND_UP),
(1.0, 1, _PyTime_ROUND_UP),
(1.9, 2, _PyTime_ROUND_UP),
):
self.assertEqual(pytime_object_to_time_t(obj), time_t)
self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t)
rnd = _PyTime_ROUND_DOWN
for invalid in self.invalid_values:
self.assertRaises(OverflowError, pytime_object_to_time_t, invalid)
self.assertRaises(OverflowError,
pytime_object_to_time_t, invalid, rnd)
@support.cpython_only
def test_timeval(self):
from _testcapi import pytime_object_to_timeval
for obj, timeval in (
(0, (0, 0)),
(-1, (-1, 0)),
(-1.0, (-1, 0)),
(1e-6, (0, 1)),
(-1e-6, (-1, 999999)),
(-1.2, (-2, 800000)),
(1.1234560, (1, 123456)),
(1.1234569, (1, 123456)),
(-1.1234560, (-2, 876544)),
(-1.1234561, (-2, 876543)),
for obj, timeval, rnd in (
# Round towards zero
(0, (0, 0), _PyTime_ROUND_DOWN),
(-1, (-1, 0), _PyTime_ROUND_DOWN),
(-1.0, (-1, 0), _PyTime_ROUND_DOWN),
(1e-6, (0, 1), _PyTime_ROUND_DOWN),
(1e-7, (0, 0), _PyTime_ROUND_DOWN),
(-1e-6, (-1, 999999), _PyTime_ROUND_DOWN),
(-1e-7, (-1, 999999), _PyTime_ROUND_DOWN),
(-1.2, (-2, 800000), _PyTime_ROUND_DOWN),
(0.9999999, (0, 999999), _PyTime_ROUND_DOWN),
(0.0000041, (0, 4), _PyTime_ROUND_DOWN),
(1.1234560, (1, 123456), _PyTime_ROUND_DOWN),
(1.1234569, (1, 123456), _PyTime_ROUND_DOWN),
(-0.0000040, (-1, 999996), _PyTime_ROUND_DOWN),
(-0.0000041, (-1, 999995), _PyTime_ROUND_DOWN),
(-1.1234560, (-2, 876544), _PyTime_ROUND_DOWN),
(-1.1234561, (-2, 876543), _PyTime_ROUND_DOWN),
# Round away from zero
(0, (0, 0), _PyTime_ROUND_UP),
(-1, (-1, 0), _PyTime_ROUND_UP),
(-1.0, (-1, 0), _PyTime_ROUND_UP),
(1e-6, (0, 1), _PyTime_ROUND_UP),
(1e-7, (0, 1), _PyTime_ROUND_UP),
(-1e-6, (-1, 999999), _PyTime_ROUND_UP),
(-1e-7, (-1, 999999), _PyTime_ROUND_UP),
(-1.2, (-2, 800000), _PyTime_ROUND_UP),
(0.9999999, (1, 0), _PyTime_ROUND_UP),
(0.0000041, (0, 5), _PyTime_ROUND_UP),
(1.1234560, (1, 123457), _PyTime_ROUND_UP),
(1.1234569, (1, 123457), _PyTime_ROUND_UP),
(-0.0000040, (-1, 999996), _PyTime_ROUND_UP),
(-0.0000041, (-1, 999995), _PyTime_ROUND_UP),
(-1.1234560, (-2, 876544), _PyTime_ROUND_UP),
(-1.1234561, (-2, 876543), _PyTime_ROUND_UP),
):
self.assertEqual(pytime_object_to_timeval(obj), timeval)
with self.subTest(obj=obj, round=rnd, timeval=timeval):
self.assertEqual(pytime_object_to_timeval(obj, rnd), timeval)
rnd = _PyTime_ROUND_DOWN
for invalid in self.invalid_values:
self.assertRaises(OverflowError, pytime_object_to_timeval, invalid)
self.assertRaises(OverflowError,
pytime_object_to_timeval, invalid, rnd)
@support.cpython_only
def test_timespec(self):
from _testcapi import pytime_object_to_timespec
for obj, timespec in (
(0, (0, 0)),
(-1, (-1, 0)),
(-1.0, (-1, 0)),
(1e-9, (0, 1)),
(-1e-9, (-1, 999999999)),
(-1.2, (-2, 800000000)),
(1.1234567890, (1, 123456789)),
(1.1234567899, (1, 123456789)),
(-1.1234567890, (-2, 876543211)),
(-1.1234567891, (-2, 876543210)),
for obj, timespec, rnd in (
# Round towards zero
(0, (0, 0), _PyTime_ROUND_DOWN),
(-1, (-1, 0), _PyTime_ROUND_DOWN),
(-1.0, (-1, 0), _PyTime_ROUND_DOWN),
(1e-9, (0, 1), _PyTime_ROUND_DOWN),
(1e-10, (0, 0), _PyTime_ROUND_DOWN),
(-1e-9, (-1, 999999999), _PyTime_ROUND_DOWN),
(-1e-10, (-1, 999999999), _PyTime_ROUND_DOWN),
(-1.2, (-2, 800000000), _PyTime_ROUND_DOWN),
(0.9999999999, (0, 999999999), _PyTime_ROUND_DOWN),
(1.1234567890, (1, 123456789), _PyTime_ROUND_DOWN),
(1.1234567899, (1, 123456789), _PyTime_ROUND_DOWN),
(-1.1234567890, (-2, 876543211), _PyTime_ROUND_DOWN),
(-1.1234567891, (-2, 876543210), _PyTime_ROUND_DOWN),
# Round away from zero
(0, (0, 0), _PyTime_ROUND_UP),
(-1, (-1, 0), _PyTime_ROUND_UP),
(-1.0, (-1, 0), _PyTime_ROUND_UP),
(1e-9, (0, 1), _PyTime_ROUND_UP),
(1e-10, (0, 1), _PyTime_ROUND_UP),
(-1e-9, (-1, 999999999), _PyTime_ROUND_UP),
(-1e-10, (-1, 999999999), _PyTime_ROUND_UP),
(-1.2, (-2, 800000000), _PyTime_ROUND_UP),
(0.9999999999, (1, 0), _PyTime_ROUND_UP),
(1.1234567890, (1, 123456790), _PyTime_ROUND_UP),
(1.1234567899, (1, 123456790), _PyTime_ROUND_UP),
(-1.1234567890, (-2, 876543211), _PyTime_ROUND_UP),
(-1.1234567891, (-2, 876543210), _PyTime_ROUND_UP),
):
self.assertEqual(pytime_object_to_timespec(obj), timespec)
with self.subTest(obj=obj, round=rnd, timespec=timespec):
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
rnd = _PyTime_ROUND_DOWN
for invalid in self.invalid_values:
self.assertRaises(OverflowError, pytime_object_to_timespec, invalid)
self.assertRaises(OverflowError,
pytime_object_to_timespec, invalid, rnd)
@unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
def test_localtime_timezone(self):

View file

@ -25,6 +25,9 @@ Core and Builtins
Library
-------
- Issue #20320: select.select() and select.kqueue.control() now round the
timeout aways from zero, instead of rounding towards zero.
- Issue #20616: Add a format() method to tracemalloc.Traceback.
- Issue #19744: the ensurepip installation step now just prints a warning to

View file

@ -2459,7 +2459,7 @@ date_local_from_object(PyObject *cls, PyObject *obj)
struct tm *tm;
time_t t;
if (_PyTime_ObjectToTime_t(obj, &t) == -1)
if (_PyTime_ObjectToTime_t(obj, &t, _PyTime_ROUND_DOWN) == -1)
return NULL;
tm = localtime(&t);
@ -4127,7 +4127,7 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp,
time_t timet;
long us;
if (_PyTime_ObjectToTimeval(timestamp, &timet, &us) == -1)
if (_PyTime_ObjectToTimeval(timestamp, &timet, &us, _PyTime_ROUND_DOWN) == -1)
return NULL;
return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo);
}

View file

@ -2516,14 +2516,27 @@ run_in_subinterp(PyObject *self, PyObject *args)
return PyLong_FromLong(r);
}
static int
check_time_rounding(int round)
{
if (round != _PyTime_ROUND_DOWN && round != _PyTime_ROUND_UP) {
PyErr_SetString(PyExc_ValueError, "invalid rounding");
return -1;
}
return 0;
}
static PyObject *
test_pytime_object_to_time_t(PyObject *self, PyObject *args)
{
PyObject *obj;
time_t sec;
if (!PyArg_ParseTuple(args, "O:pytime_object_to_time_t", &obj))
int round;
if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_time_t", &obj, &round))
return NULL;
if (_PyTime_ObjectToTime_t(obj, &sec) == -1)
if (check_time_rounding(round) < 0)
return NULL;
if (_PyTime_ObjectToTime_t(obj, &sec, round) == -1)
return NULL;
return _PyLong_FromTime_t(sec);
}
@ -2534,9 +2547,12 @@ test_pytime_object_to_timeval(PyObject *self, PyObject *args)
PyObject *obj;
time_t sec;
long usec;
if (!PyArg_ParseTuple(args, "O:pytime_object_to_timeval", &obj))
int round;
if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_timeval", &obj, &round))
return NULL;
if (_PyTime_ObjectToTimeval(obj, &sec, &usec) == -1)
if (check_time_rounding(round) < 0)
return NULL;
if (_PyTime_ObjectToTimeval(obj, &sec, &usec, round) == -1)
return NULL;
return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), usec);
}
@ -2547,9 +2563,12 @@ test_pytime_object_to_timespec(PyObject *self, PyObject *args)
PyObject *obj;
time_t sec;
long nsec;
if (!PyArg_ParseTuple(args, "O:pytime_object_to_timespec", &obj))
int round;
if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_timespec", &obj, &round))
return NULL;
if (_PyTime_ObjectToTimespec(obj, &sec, &nsec) == -1)
if (check_time_rounding(round) < 0)
return NULL;
if (_PyTime_ObjectToTimespec(obj, &sec, &nsec, round) == -1)
return NULL;
return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), nsec);
}

View file

@ -4901,9 +4901,9 @@ posix_utime(PyObject *self, PyObject *args, PyObject *kwargs)
}
utime.now = 0;
if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0),
&a_sec, &a_nsec) == -1 ||
&a_sec, &a_nsec, _PyTime_ROUND_DOWN) == -1 ||
_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1),
&m_sec, &m_nsec) == -1) {
&m_sec, &m_nsec, _PyTime_ROUND_DOWN) == -1) {
goto exit;
}
utime.atime_s = a_sec;

View file

@ -214,7 +214,8 @@ select_select(PyObject *self, PyObject *args)
else {
#ifdef MS_WINDOWS
time_t sec;
if (_PyTime_ObjectToTimeval(tout, &sec, &tv.tv_usec) == -1)
if (_PyTime_ObjectToTimeval(tout, &sec, &tv.tv_usec,
_PyTime_ROUND_UP) == -1)
return NULL;
assert(sizeof(tv.tv_sec) == sizeof(long));
#if SIZEOF_TIME_T > SIZEOF_LONG
@ -229,7 +230,8 @@ select_select(PyObject *self, PyObject *args)
/* 64-bit OS X has struct timeval.tv_usec as an int (and thus still 4
bytes as required), but no longer defined by a long. */
long tv_usec;
if (_PyTime_ObjectToTimeval(tout, &tv.tv_sec, &tv_usec) == -1)
if (_PyTime_ObjectToTimeval(tout, &tv.tv_sec, &tv_usec,
_PyTime_ROUND_UP) == -1)
return NULL;
tv.tv_usec = tv_usec;
#endif
@ -2037,8 +2039,8 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
ptimeoutspec = NULL;
}
else if (PyNumber_Check(otimeout)) {
if (_PyTime_ObjectToTimespec(otimeout,
&timeout.tv_sec, &timeout.tv_nsec) == -1)
if (_PyTime_ObjectToTimespec(otimeout, &timeout.tv_sec,
&timeout.tv_nsec, _PyTime_ROUND_UP) == -1)
return NULL;
if (timeout.tv_sec < 0) {

View file

@ -799,7 +799,8 @@ signal_sigtimedwait(PyObject *self, PyObject *args)
&signals, &timeout))
return NULL;
if (_PyTime_ObjectToTimespec(timeout, &tv_sec, &tv_nsec) == -1)
if (_PyTime_ObjectToTimespec(timeout, &tv_sec, &tv_nsec,
_PyTime_ROUND_DOWN) == -1)
return NULL;
buf.tv_sec = tv_sec;
buf.tv_nsec = tv_nsec;

View file

@ -193,7 +193,7 @@ time_clock_settime(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj))
return NULL;
if (_PyTime_ObjectToTimespec(obj, &tv_sec, &tv_nsec) == -1)
if (_PyTime_ObjectToTimespec(obj, &tv_sec, &tv_nsec, _PyTime_ROUND_DOWN) == -1)
return NULL;
tp.tv_sec = tv_sec;
tp.tv_nsec = tv_nsec;
@ -341,7 +341,7 @@ parse_time_t_args(PyObject *args, char *format, time_t *pwhen)
whent = time(NULL);
}
else {
if (_PyTime_ObjectToTime_t(ot, &whent) == -1)
if (_PyTime_ObjectToTime_t(ot, &whent, _PyTime_ROUND_DOWN) == -1)
return 0;
}
*pwhen = whent;

View file

@ -152,7 +152,7 @@ _PyLong_FromTime_t(time_t t)
static int
_PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator,
double denominator)
double denominator, _PyTime_round_t round)
{
assert(denominator <= LONG_MAX);
if (PyFloat_Check(obj)) {
@ -167,6 +167,20 @@ _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator,
intpart -= 1.0;
}
floatpart *= denominator;
if (round == _PyTime_ROUND_UP) {
if (intpart >= 0) {
floatpart = ceil(floatpart);
if (floatpart >= denominator) {
floatpart = 0.0;
intpart += 1.0;
}
}
else {
floatpart = floor(floatpart);
}
}
*sec = (time_t)intpart;
err = intpart - (double)*sec;
if (err <= -1.0 || err >= 1.0) {
@ -174,7 +188,6 @@ _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator,
return -1;
}
floatpart *= denominator;
*numerator = (long)floatpart;
return 0;
}
@ -188,12 +201,18 @@ _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator,
}
int
_PyTime_ObjectToTime_t(PyObject *obj, time_t *sec)
_PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round)
{
if (PyFloat_Check(obj)) {
double d, intpart, err;
d = PyFloat_AsDouble(obj);
if (round == _PyTime_ROUND_UP) {
if (d >= 0)
d = ceil(d);
else
d = floor(d);
}
(void)modf(d, &intpart);
*sec = (time_t)intpart;
@ -213,15 +232,17 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec)
}
int
_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec)
_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec,
_PyTime_round_t round)
{
return _PyTime_ObjectToDenominator(obj, sec, nsec, 1e9);
return _PyTime_ObjectToDenominator(obj, sec, nsec, 1e9, round);
}
int
_PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec)
_PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec,
_PyTime_round_t round)
{
return _PyTime_ObjectToDenominator(obj, sec, usec, 1e6);
return _PyTime_ObjectToDenominator(obj, sec, usec, 1e6, round);
}
void