From 11da8e24ba1090b9d55f3451345f0724055f125f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 21 Jan 2014 01:48:28 +0100 Subject: [PATCH] Issue #20311: selector.PollSelector.select() now rounds the timeout away from zero, instead of rounding towards zero. For example, a timeout of one microsecond is now rounded to one millisecond, instead of being rounded to zero. Move also a test in test_epoll which was moved by my previous merge. --- Lib/selectors.py | 8 +++++++- Lib/test/test_epoll.py | 23 +++++++++++------------ Lib/test/test_selectors.py | 18 +++++++++++++++++- Misc/NEWS | 5 +++++ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Lib/selectors.py b/Lib/selectors.py index a44d5e96b40..63392f5eb93 100644 --- a/Lib/selectors.py +++ b/Lib/selectors.py @@ -8,6 +8,7 @@ This module allows high-level and efficient I/O multiplexing, built upon the from abc import ABCMeta, abstractmethod from collections import namedtuple, Mapping import functools +import math import select import sys @@ -351,7 +352,12 @@ if hasattr(select, 'poll'): return key def select(self, timeout=None): - timeout = None if timeout is None else max(int(1000 * timeout), 0) + if timeout is None: + timeout = None + elif timeout < 0: + timeout = 0 + else: + timeout = int(math.ceil(timeout * 1000.0)) ready = [] try: fd_event_list = self._poll.poll(timeout) diff --git a/Lib/test/test_epoll.py b/Lib/test/test_epoll.py index 22e98962498..094b0f07d70 100644 --- a/Lib/test/test_epoll.py +++ b/Lib/test/test_epoll.py @@ -47,18 +47,6 @@ class TestEPoll(unittest.TestCase): self.serverSocket.listen(1) self.connections = [self.serverSocket] - def test_timeout_rounding(self): - # epoll_wait() has a resolution of 1 millisecond, check if the timeout - # is correctly rounded to the upper bound - epoll = select.epoll() - self.addCleanup(epoll.close) - for timeout in (1e-2, 1e-3, 1e-4): - t0 = time.perf_counter() - epoll.poll(timeout) - dt = time.perf_counter() - t0 - self.assertGreaterEqual(dt, timeout) - - def tearDown(self): for skt in self.connections: skt.close() @@ -266,6 +254,17 @@ class TestEPoll(unittest.TestCase): self.addCleanup(epoll.close) self.assertEqual(os.get_inheritable(epoll.fileno()), False) + def test_timeout_rounding(self): + # epoll_wait() has a resolution of 1 millisecond, check if the timeout + # is correctly rounded to the upper bound + epoll = select.epoll() + self.addCleanup(epoll.close) + for timeout in (1e-2, 1e-3, 1e-4): + t0 = time.perf_counter() + epoll.poll(timeout) + dt = time.perf_counter() - t0 + self.assertGreaterEqual(dt, timeout) + def test_main(): support.run_unittest(TestEPoll) diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py index 34edd7630dd..d306aef773c 100644 --- a/Lib/test/test_selectors.py +++ b/Lib/test/test_selectors.py @@ -5,7 +5,7 @@ import selectors import signal import socket from test import support -from time import sleep +from time import sleep, perf_counter import unittest import unittest.mock try: @@ -363,6 +363,22 @@ class BaseSelectorTestCase(unittest.TestCase): self.assertFalse(s.select(2)) self.assertLess(time() - t, 2.5) + def test_timeout_rounding(self): + # Issue #20311: Timeout must be rounded away from zero to wait *at + # least* timeout seconds. For example, epoll_wait() has a resolution of + # 1 ms (10^-3), epoll.select(0.0001) must wait 1 ms, not 0 ms. + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = self.make_socketpair() + s.register(rd, selectors.EVENT_READ) + + for timeout in (1e-2, 1e-3, 1e-4): + t0 = perf_counter() + s.select(timeout) + dt = perf_counter() - t0 + self.assertGreaterEqual(dt, timeout) + class ScalableSelectorMixIn: diff --git a/Misc/NEWS b/Misc/NEWS index 88921d4aa78..ad37fa5f430 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -25,6 +25,11 @@ Core and Builtins Library ------- +- Issue #20311: selector.PollSelector.select() now rounds the timeout away from + zero, instead of rounding towards zero. For example, a timeout of one + microsecond is now rounded to one millisecond, instead of being rounded to + zero. + - Issue #20311: select.epoll.poll() now rounds the timeout away from zero, instead of rounding towards zero. For example, a timeout of one microsecond is now rounded to one millisecond, instead of being rounded to zero.