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) (tv_end.tv_usec - tv_start.tv_usec) * 0.000001)
#ifndef Py_LIMITED_API #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. */ /* Convert a number of seconds, int or float, to time_t. */
PyAPI_FUNC(int) _PyTime_ObjectToTime_t( PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
PyObject *obj, PyObject *obj,
time_t *sec); time_t *sec,
_PyTime_round_t);
/* Convert a time_t to a PyLong. */ /* Convert a time_t to a PyLong. */
PyAPI_FUNC(PyObject *) _PyLong_FromTime_t( PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
@ -72,7 +81,8 @@ PyAPI_FUNC(time_t) _PyLong_AsTime_t(
PyAPI_FUNC(int) _PyTime_ObjectToTimeval( PyAPI_FUNC(int) _PyTime_ObjectToTimeval(
PyObject *obj, PyObject *obj,
time_t *sec, time_t *sec,
long *usec); long *usec,
_PyTime_round_t);
/* Convert a number of seconds, int or float, to a timespec structure. /* Convert a number of seconds, int or float, to a timespec structure.
nsec is in the range [0; 999999999] and rounded towards zero. 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( PyAPI_FUNC(int) _PyTime_ObjectToTimespec(
PyObject *obj, PyObject *obj,
time_t *sec, time_t *sec,
long *nsec); long *nsec,
_PyTime_round_t);
#endif #endif
/* Dummy to force linking. */ /* Dummy to force linking. */

View File

@ -14,6 +14,8 @@ except ImportError:
SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4
TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1 TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1
TIME_MINYEAR = -TIME_MAXYEAR - 1 TIME_MINYEAR = -TIME_MAXYEAR - 1
_PyTime_ROUND_DOWN = 0
_PyTime_ROUND_UP = 1
class TimeTestCase(unittest.TestCase): class TimeTestCase(unittest.TestCase):
@ -585,58 +587,116 @@ class TestPytime(unittest.TestCase):
@support.cpython_only @support.cpython_only
def test_time_t(self): def test_time_t(self):
from _testcapi import pytime_object_to_time_t from _testcapi import pytime_object_to_time_t
for obj, time_t in ( for obj, time_t, rnd in (
(0, 0), # Round towards zero
(-1, -1), (0, 0, _PyTime_ROUND_DOWN),
(-1.0, -1), (-1, -1, _PyTime_ROUND_DOWN),
(-1.9, -1), (-1.0, -1, _PyTime_ROUND_DOWN),
(1.0, 1), (-1.9, -1, _PyTime_ROUND_DOWN),
(1.9, 1), (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: 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 @support.cpython_only
def test_timeval(self): def test_timeval(self):
from _testcapi import pytime_object_to_timeval from _testcapi import pytime_object_to_timeval
for obj, timeval in ( for obj, timeval, rnd in (
(0, (0, 0)), # Round towards zero
(-1, (-1, 0)), (0, (0, 0), _PyTime_ROUND_DOWN),
(-1.0, (-1, 0)), (-1, (-1, 0), _PyTime_ROUND_DOWN),
(1e-6, (0, 1)), (-1.0, (-1, 0), _PyTime_ROUND_DOWN),
(-1e-6, (-1, 999999)), (1e-6, (0, 1), _PyTime_ROUND_DOWN),
(-1.2, (-2, 800000)), (1e-7, (0, 0), _PyTime_ROUND_DOWN),
(1.1234560, (1, 123456)), (-1e-6, (-1, 999999), _PyTime_ROUND_DOWN),
(1.1234569, (1, 123456)), (-1e-7, (-1, 999999), _PyTime_ROUND_DOWN),
(-1.1234560, (-2, 876544)), (-1.2, (-2, 800000), _PyTime_ROUND_DOWN),
(-1.1234561, (-2, 876543)), (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: 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 @support.cpython_only
def test_timespec(self): def test_timespec(self):
from _testcapi import pytime_object_to_timespec from _testcapi import pytime_object_to_timespec
for obj, timespec in ( for obj, timespec, rnd in (
(0, (0, 0)), # Round towards zero
(-1, (-1, 0)), (0, (0, 0), _PyTime_ROUND_DOWN),
(-1.0, (-1, 0)), (-1, (-1, 0), _PyTime_ROUND_DOWN),
(1e-9, (0, 1)), (-1.0, (-1, 0), _PyTime_ROUND_DOWN),
(-1e-9, (-1, 999999999)), (1e-9, (0, 1), _PyTime_ROUND_DOWN),
(-1.2, (-2, 800000000)), (1e-10, (0, 0), _PyTime_ROUND_DOWN),
(1.1234567890, (1, 123456789)), (-1e-9, (-1, 999999999), _PyTime_ROUND_DOWN),
(1.1234567899, (1, 123456789)), (-1e-10, (-1, 999999999), _PyTime_ROUND_DOWN),
(-1.1234567890, (-2, 876543211)), (-1.2, (-2, 800000000), _PyTime_ROUND_DOWN),
(-1.1234567891, (-2, 876543210)), (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: 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") @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
def test_localtime_timezone(self): def test_localtime_timezone(self):

View File

@ -25,6 +25,9 @@ Core and Builtins
Library 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 #20616: Add a format() method to tracemalloc.Traceback.
- Issue #19744: the ensurepip installation step now just prints a warning to - 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; struct tm *tm;
time_t t; time_t t;
if (_PyTime_ObjectToTime_t(obj, &t) == -1) if (_PyTime_ObjectToTime_t(obj, &t, _PyTime_ROUND_DOWN) == -1)
return NULL; return NULL;
tm = localtime(&t); tm = localtime(&t);
@ -4127,7 +4127,7 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp,
time_t timet; time_t timet;
long us; long us;
if (_PyTime_ObjectToTimeval(timestamp, &timet, &us) == -1) if (_PyTime_ObjectToTimeval(timestamp, &timet, &us, _PyTime_ROUND_DOWN) == -1)
return NULL; return NULL;
return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo); 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); 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 * static PyObject *
test_pytime_object_to_time_t(PyObject *self, PyObject *args) test_pytime_object_to_time_t(PyObject *self, PyObject *args)
{ {
PyObject *obj; PyObject *obj;
time_t sec; 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; 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 NULL;
return _PyLong_FromTime_t(sec); return _PyLong_FromTime_t(sec);
} }
@ -2534,9 +2547,12 @@ test_pytime_object_to_timeval(PyObject *self, PyObject *args)
PyObject *obj; PyObject *obj;
time_t sec; time_t sec;
long usec; 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; 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 NULL;
return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), usec); return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), usec);
} }
@ -2547,9 +2563,12 @@ test_pytime_object_to_timespec(PyObject *self, PyObject *args)
PyObject *obj; PyObject *obj;
time_t sec; time_t sec;
long nsec; 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; 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 NULL;
return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), nsec); 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; utime.now = 0;
if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 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), _PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1),
&m_sec, &m_nsec) == -1) { &m_sec, &m_nsec, _PyTime_ROUND_DOWN) == -1) {
goto exit; goto exit;
} }
utime.atime_s = a_sec; utime.atime_s = a_sec;

View File

@ -214,7 +214,8 @@ select_select(PyObject *self, PyObject *args)
else { else {
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
time_t sec; 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; return NULL;
assert(sizeof(tv.tv_sec) == sizeof(long)); assert(sizeof(tv.tv_sec) == sizeof(long));
#if SIZEOF_TIME_T > 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 /* 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. */ bytes as required), but no longer defined by a long. */
long tv_usec; 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; return NULL;
tv.tv_usec = tv_usec; tv.tv_usec = tv_usec;
#endif #endif
@ -2037,8 +2039,8 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
ptimeoutspec = NULL; ptimeoutspec = NULL;
} }
else if (PyNumber_Check(otimeout)) { else if (PyNumber_Check(otimeout)) {
if (_PyTime_ObjectToTimespec(otimeout, if (_PyTime_ObjectToTimespec(otimeout, &timeout.tv_sec,
&timeout.tv_sec, &timeout.tv_nsec) == -1) &timeout.tv_nsec, _PyTime_ROUND_UP) == -1)
return NULL; return NULL;
if (timeout.tv_sec < 0) { if (timeout.tv_sec < 0) {

View File

@ -799,7 +799,8 @@ signal_sigtimedwait(PyObject *self, PyObject *args)
&signals, &timeout)) &signals, &timeout))
return NULL; 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; return NULL;
buf.tv_sec = tv_sec; buf.tv_sec = tv_sec;
buf.tv_nsec = tv_nsec; 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)) if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj))
return NULL; 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; return NULL;
tp.tv_sec = tv_sec; tp.tv_sec = tv_sec;
tp.tv_nsec = tv_nsec; tp.tv_nsec = tv_nsec;
@ -341,7 +341,7 @@ parse_time_t_args(PyObject *args, char *format, time_t *pwhen)
whent = time(NULL); whent = time(NULL);
} }
else { else {
if (_PyTime_ObjectToTime_t(ot, &whent) == -1) if (_PyTime_ObjectToTime_t(ot, &whent, _PyTime_ROUND_DOWN) == -1)
return 0; return 0;
} }
*pwhen = whent; *pwhen = whent;

View File

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