Skip to content

Commit c3c994a

Browse files
Select/poll improvements.
1 parent 08c9e5b commit c3c994a

File tree

3 files changed

+100
-24
lines changed

3 files changed

+100
-24
lines changed

prompt_toolkit/eventloop/inputhook.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
"""
2525
from __future__ import unicode_literals
2626
import os
27-
import select
2827
import threading
28+
from .select import select_fds
2929

3030
__all__ = (
3131
'InputHookContext',
@@ -80,7 +80,7 @@ def thread():
8080
# the above thread.
8181
# This appears to be only required when gevent.monkey.patch_all()
8282
# has been executed. Otherwise it doesn't do much.
83-
select.select([self._r], [], [])
83+
select_fds([self._r], timeout=None)
8484

8585
os.read(self._r, 1024)
8686
except OSError:

prompt_toolkit/eventloop/posix.py

+3-22
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
from __future__ import unicode_literals
22
import datetime
3-
import errno
43
import fcntl
54
import os
65
import random
7-
import select
86
import signal
97
import threading
108

@@ -16,6 +14,7 @@
1614
from .inputhook import InputHookContext
1715
from .posix_utils import PosixStdinReader
1816
from .utils import TimeIt
17+
from .select import select_fds
1918

2019
__all__ = (
2120
'PosixEventLoop',
@@ -181,8 +180,8 @@ def _ready_for_reading(self, timeout=None):
181180
Return the file descriptors that are ready for reading.
182181
"""
183182
read_fds = list(self._read_fds.keys())
184-
r, _, _ =_select(read_fds, [], [], timeout)
185-
return r
183+
fds = select_fds(read_fds, timeout)
184+
return fds
186185

187186
def received_winch(self):
188187
"""
@@ -266,24 +265,6 @@ def remove_reader(self, fd):
266265
del self._read_fds[fd]
267266

268267

269-
def _select(*args, **kwargs):
270-
"""
271-
Wrapper around select.select.
272-
273-
When the SIGWINCH signal is handled, other system calls, like select
274-
are aborted in Python. This wrapper will retry the system call.
275-
"""
276-
while True:
277-
try:
278-
return select.select(*args, **kwargs)
279-
except select.error as e:
280-
# Retry select call when EINTR
281-
if e.args and e.args[0] == errno.EINTR:
282-
continue
283-
else:
284-
raise
285-
286-
287268
class call_on_sigwinch(object):
288269
"""
289270
Context manager which Installs a SIGWINCH callback.

prompt_toolkit/eventloop/select.py

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import sys
2+
import select
3+
import errno
4+
5+
__all__ = (
6+
'select_fds',
7+
)
8+
9+
def _fd_to_int(fd):
10+
if isinstance(fd, int):
11+
return fd
12+
else:
13+
return fd.fileno()
14+
15+
def select_fds(read_fds, timeout):
16+
"""
17+
Wait for a list of file descriptors (`read_fds`) to become ready for
18+
reading. This chooses the most appropriate select-tool for use in
19+
prompt-toolkit.
20+
21+
Note: This is an internal API that shouldn't be used for external projects.
22+
"""
23+
# Map to ensure that we return the objects that were passed in originally.
24+
# Whether they are a fd integer or an object that has a fileno().
25+
# (The 'poll' implementation for instance, returns always integers.)
26+
fd_map = {_fd_to_int(fd): fd for fd in read_fds}
27+
28+
if sys.version_info >= (3, 5):
29+
# Use of the 'select' module, that was introduced in Python3.4.
30+
# We don't use it before 3.5 however, because this is the point where
31+
# this module retries interrupted system calls.
32+
result = _python3_selectors(read_fds, timeout)
33+
else:
34+
try:
35+
# First, try the 'select' module. This is the most universal, and
36+
# powerful enough in our case.
37+
result = _select(read_fds, timeout)
38+
except ValueError:
39+
# When we have more than 1024 open file descriptors, we'll always
40+
# get a "ValueError: filedescriptor out of range in select()" for
41+
# 'select'. In this case, retry, using 'poll' instead.
42+
result = _poll(read_fds, timeout)
43+
44+
return [fd_map[_fd_to_int(fd)] for fd in result]
45+
46+
47+
def _python3_selectors(read_fds, timeout):
48+
"""
49+
Use of the Python3 'selectors' module.
50+
51+
NOTE: Only use on Python 3.5 or newer!
52+
"""
53+
import selectors # Inline import: Python3 only!
54+
sel = selectors.DefaultSelector()
55+
56+
for fd in read_fds:
57+
sel.register(fd, selectors.EVENT_READ, None)
58+
59+
events = sel.select(timeout=timeout)
60+
try:
61+
return [key.fileobj for key, mask in events]
62+
finally:
63+
sel.close()
64+
65+
66+
def _poll(read_fds, timeout):
67+
"""
68+
Use 'poll', to wait for any of the given `read_fds` to become ready.
69+
"""
70+
from select import poll
71+
72+
p = poll()
73+
for fd in read_fds:
74+
p.register(fd, select.POLLIN)
75+
76+
tuples = p.poll(timeout) # Returns (fd, event) tuples.
77+
return [t[0] for t in tuples]
78+
79+
80+
def _select(read_fds, timeout):
81+
"""
82+
Wrapper around select.select.
83+
84+
When the SIGWINCH signal is handled, other system calls, like select
85+
are aborted in Python. This wrapper will retry the system call.
86+
"""
87+
while True:
88+
try:
89+
return select.select(read_fds, [], [], timeout)[0]
90+
except select.error as e:
91+
# Retry select call when EINTR
92+
if e.args and e.args[0] == errno.EINTR:
93+
continue
94+
else:
95+
raise

0 commit comments

Comments
 (0)