Skip to content

Commit 80d84c8

Browse files
Merge heads
2 parents d5d818d + b310173 commit 80d84c8

File tree

7 files changed

+181
-69
lines changed

7 files changed

+181
-69
lines changed

Doc/library/select.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ object.
249249
returning. If *timeout* is omitted, -1, or :const:`None`, the call will
250250
block until there is an event for this poll object.
251251

252+
.. versionchanged:: 3.5
253+
The function is now retried with a recomputed timeout when interrupted by
254+
a signal, except if the signal handler raises an exception (see
255+
:pep:`475` for the rationale), instead of raising
256+
:exc:`InterruptedError`.
257+
252258

253259
.. _epoll-objects:
254260

@@ -454,6 +460,12 @@ Kqueue Objects
454460
- max_events must be 0 or a positive integer
455461
- timeout in seconds (floats possible)
456462

463+
.. versionchanged:: 3.5
464+
The function is now retried with a recomputed timeout when interrupted by
465+
a signal, except if the signal handler raises an exception (see
466+
:pep:`475` for the rationale), instead of raising
467+
:exc:`InterruptedError`.
468+
457469

458470
.. _kevent-objects:
459471

Doc/library/selectors.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,12 @@ below:
159159
timeout has elapsed if the current process receives a signal: in this
160160
case, an empty list will be returned.
161161

162+
.. versionchanged:: 3.5
163+
The selector is now retried with a recomputed timeout when interrupted
164+
by a signal if the signal handler did not raise an exception (see
165+
:pep:`475` for the rationale), instead of returning an empty list
166+
of events before the timeout.
167+
162168
.. method:: close()
163169

164170
Close the selector.

Doc/whatsnew/3.5.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,9 +619,10 @@ Changes in the Python API
619619
instead of raising :exc:`InterruptedError` if the signal handler does not
620620
raise an exception:
621621

622-
- :func:`os.open`, :func:`open`
622+
- :func:`open`, :func:`os.open`, :func:`io.open`
623623
- :func:`os.read`, :func:`os.write`
624-
- :func:`select.select`, :func:`select.poll.poll`, :func:`select.epoll.poll`
624+
- :func:`select.select`, :func:`select.poll.poll`, :func:`select.epoll.poll`,
625+
:func:`select.kqueue.control`, :func:`select.devpoll.poll`
625626
- :func:`time.sleep`
626627

627628
* Before Python 3.5, a :class:`datetime.time` object was considered to be false

Lib/selectors.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -479,11 +479,10 @@ def select(self, timeout=None):
479479
# devpoll() has a resolution of 1 millisecond, round away from
480480
# zero to wait *at least* timeout seconds.
481481
timeout = math.ceil(timeout * 1e3)
482+
483+
fd_event_list = self._devpoll.poll(timeout)
484+
482485
ready = []
483-
try:
484-
fd_event_list = self._devpoll.poll(timeout)
485-
except InterruptedError:
486-
return ready
487486
for fd, event in fd_event_list:
488487
events = 0
489488
if event & ~select.POLLIN:
@@ -549,11 +548,9 @@ def unregister(self, fileobj):
549548
def select(self, timeout=None):
550549
timeout = None if timeout is None else max(timeout, 0)
551550
max_ev = len(self._fd_to_key)
551+
kev_list = self._kqueue.control(None, max_ev, timeout)
552+
552553
ready = []
553-
try:
554-
kev_list = self._kqueue.control(None, max_ev, timeout)
555-
except InterruptedError:
556-
return ready
557554
for kev in kev_list:
558555
fd = kev.ident
559556
flag = kev.filter

Lib/test/eintrdata/eintr_tester.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,8 @@ class SelectEINTRTest(EINTRBaseTest):
315315
def test_select(self):
316316
t0 = time.monotonic()
317317
select.select([], [], [], self.sleep_time)
318-
self.stop_alarm()
319318
dt = time.monotonic() - t0
319+
self.stop_alarm()
320320
self.assertGreaterEqual(dt, self.sleep_time)
321321

322322
@unittest.skipUnless(hasattr(select, 'poll'), 'need select.poll')
@@ -325,8 +325,8 @@ def test_poll(self):
325325

326326
t0 = time.monotonic()
327327
poller.poll(self.sleep_time * 1e3)
328-
self.stop_alarm()
329328
dt = time.monotonic() - t0
329+
self.stop_alarm()
330330
self.assertGreaterEqual(dt, self.sleep_time)
331331

332332
@unittest.skipUnless(hasattr(select, 'epoll'), 'need select.epoll')
@@ -336,8 +336,30 @@ def test_epoll(self):
336336

337337
t0 = time.monotonic()
338338
poller.poll(self.sleep_time)
339+
dt = time.monotonic() - t0
340+
self.stop_alarm()
341+
self.assertGreaterEqual(dt, self.sleep_time)
342+
343+
@unittest.skipUnless(hasattr(select, 'kqueue'), 'need select.kqueue')
344+
def test_kqueue(self):
345+
kqueue = select.kqueue()
346+
self.addCleanup(kqueue.close)
347+
348+
t0 = time.monotonic()
349+
kqueue.control(None, 1, self.sleep_time)
350+
dt = time.monotonic() - t0
339351
self.stop_alarm()
352+
self.assertGreaterEqual(dt, self.sleep_time)
353+
354+
@unittest.skipUnless(hasattr(select, 'devpoll'), 'need select.devpoll')
355+
def test_devpoll(self):
356+
poller = select.devpoll()
357+
self.addCleanup(poller.close)
358+
359+
t0 = time.monotonic()
360+
poller.poll(self.sleep_time * 1e3)
340361
dt = time.monotonic() - t0
362+
self.stop_alarm()
341363
self.assertGreaterEqual(dt, self.sleep_time)
342364

343365

Lib/test/test_selectors.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,35 @@ def test_timeout(self):
357357

358358
@unittest.skipUnless(hasattr(signal, "alarm"),
359359
"signal.alarm() required for this test")
360-
def test_select_interrupt(self):
360+
def test_select_interrupt_exc(self):
361+
s = self.SELECTOR()
362+
self.addCleanup(s.close)
363+
364+
rd, wr = self.make_socketpair()
365+
366+
class InterruptSelect(Exception):
367+
pass
368+
369+
def handler(*args):
370+
raise InterruptSelect
371+
372+
orig_alrm_handler = signal.signal(signal.SIGALRM, handler)
373+
self.addCleanup(signal.signal, signal.SIGALRM, orig_alrm_handler)
374+
self.addCleanup(signal.alarm, 0)
375+
376+
signal.alarm(1)
377+
378+
s.register(rd, selectors.EVENT_READ)
379+
t = time()
380+
# select() is interrupted by a signal which raises an exception
381+
with self.assertRaises(InterruptSelect):
382+
s.select(30)
383+
# select() was interrupted before the timeout of 30 seconds
384+
self.assertLess(time() - t, 5.0)
385+
386+
@unittest.skipUnless(hasattr(signal, "alarm"),
387+
"signal.alarm() required for this test")
388+
def test_select_interrupt_noraise(self):
361389
s = self.SELECTOR()
362390
self.addCleanup(s.close)
363391

@@ -371,8 +399,11 @@ def test_select_interrupt(self):
371399

372400
s.register(rd, selectors.EVENT_READ)
373401
t = time()
374-
self.assertFalse(s.select(2))
375-
self.assertLess(time() - t, 2.5)
402+
# select() is interrupted by a signal, but the signal handler doesn't
403+
# raise an exception, so select() should by retries with a recomputed
404+
# timeout
405+
self.assertFalse(s.select(1.5))
406+
self.assertGreaterEqual(time() - t, 1.0)
376407

377408

378409
class ScalableSelectorMixIn:

0 commit comments

Comments
 (0)