Files
1ndahous3 02f4fbbb62 Use ordinary fileno lookup in select (#9266)
* Use ordinary fileno lookup in select

Assisted-by: Codex:GPT-6

* Share select descriptor conversion

Assisted-by: Codex:GPT-6
2026-10-21 11:59:10 +09:00

202 lines
5.6 KiB
Python

import select
import signal
import socket
import sys
from testutils import assert_raises
TOO_MANY_SELECT_FDS = 4096
class Nope:
pass
class Almost:
def fileno(self):
return "fileno"
assert_raises(TypeError, select.select, 1, 2, 3)
assert_raises(TypeError, select.select, [Nope()], [], [])
assert_raises(TypeError, select.select, [Almost()], [], [])
assert_raises(TypeError, select.select, [], [], [], "not a number")
assert_raises(ValueError, select.select, [], [], [], -1)
if sys.platform in ["win32", "cygwin"]:
assert_raises(OSError, select.select, [0], [], [])
recvr = socket.socket()
recvr.bind(("127.0.0.1", 9988))
recvr.listen()
recvr.settimeout(10.0)
sendr = socket.socket()
sendr.connect(("127.0.0.1", 9988))
sendr.send(b"aaaa")
rres, wres, xres = select.select([recvr], [sendr], [])
if "win" not in sys.platform:
assert recvr in rres
assert sendr in wres
# Too many descriptors for select.select()
if sys.platform != "win32":
import resource
soft_max_fds, hard_max_fds = resource.getrlimit(resource.RLIMIT_NOFILE)
if soft_max_fds != resource.RLIM_INFINITY:
# 100 additional fds should be enough for interpreter needs
need_fds = TOO_MANY_SELECT_FDS + 100
soft_max_fds = max(soft_max_fds, need_fds)
if hard_max_fds != resource.RLIM_INFINITY:
assert hard_max_fds >= soft_max_fds, (
"Not enough file descriptors for this test"
)
resource.setrlimit(resource.RLIMIT_NOFILE, (soft_max_fds, hard_max_fds))
sockets = [s for _ in range(TOO_MANY_SELECT_FDS // 2) for s in socket.socketpair()]
assert_raises(ValueError, select.select, sockets, [], [], 0)
if sys.platform != "win32":
# Try to overflow descriptor bit mask on *nix with a single item
max_fd = -1
max_fd_sock = None
sockets.reverse()
for sock in sockets:
if sock.fileno() > max_fd:
max_fd = sock.fileno()
max_fd_sock = sock
assert_raises(ValueError, select.select, [max_fd_sock], [], [], 0)
for sock in sockets:
sock.close()
del sockets
a, b = socket.socketpair()
# CPython disallows this on *nix systems too.
assert_raises(ValueError, select.select, [a] * TOO_MANY_SELECT_FDS, [], [], 0)
a.close()
b.close()
# Windows supports up to 512 entries, above the Windows SDK default of 64.
for count in (65, 512) if sys.platform == "win32" else (65,):
pairs = []
try:
for _ in range(count):
pairs.append(socket.socketpair())
readers = [reader for reader, writer in pairs]
assert select.select(readers, [], [], 0) == ([], [], [])
for reader, writer in pairs:
writer.send(b"x")
assert select.select(readers, [], [], 0) == (readers, [], [])
if sys.platform == "win32":
assert_raises(ValueError, select.select, [readers[0]] * 513, [], [], 0)
finally:
for pair in pairs:
for sock in pair:
sock.close()
# fileno() runs while the sequence is being read, and it can mutate the very
# list it was handed.
mutable_pair, other_end = socket.socketpair()
class MutatesTheList:
def __init__(self, elements, fd):
self.elements = elements
self.fd = fd
def fileno(self):
self.elements.clear()
self.elements.append(self)
return self.fd
elements = []
elements.extend([MutatesTheList(elements, mutable_pair.fileno())] * 40)
assert select.select(elements, [], [], 0) == ([], [], [])
class GrowsTheList:
def __init__(self, elements, fd):
self.elements = elements
self.fd = fd
def fileno(self):
self.elements.append(self)
return self.fd
# A list that grows by one on every fileno() never reaches its own end, so the
# limit has to be answered during the walk rather than from the final length.
elements = []
elements.append(GrowsTheList(elements, mutable_pair.fileno()))
assert_raises(ValueError, select.select, elements, [], [], 0)
mutable_pair.close()
other_end.close()
# poll() waits with signal handlers able to run, and a handler may register on
# the same poll object.
if hasattr(select, "poll") and hasattr(signal, "setitimer"):
poller = select.poll()
idle, idle_peer = socket.socketpair()
poller.register(idle.fileno(), select.POLLIN)
handled = []
def register_from_handler(signum, frame):
poller.register(idle_peer.fileno(), select.POLLIN)
handled.append(signum)
previous = signal.signal(signal.SIGALRM, register_from_handler)
try:
signal.setitimer(signal.ITIMER_REAL, 0.05)
try:
poller.poll(1000)
except InterruptedError:
pass
finally:
signal.setitimer(signal.ITIMER_REAL, 0)
signal.signal(signal.SIGALRM, previous)
assert handled == [signal.SIGALRM], handled
idle.close()
idle_peer.close()
# fileno is an ordinary attribute, not a special method.
class InstanceFileno:
pass
class DynamicFileno:
def __getattr__(self, name):
if name == "fileno":
return lookup_reader.fileno
raise AttributeError(name)
class IndexOnly:
def __index__(self):
return lookup_reader.fileno()
lookup_reader, lookup_writer = socket.socketpair()
try:
lookup_writer.send(b"x")
instance = InstanceFileno()
instance.fileno = lookup_reader.fileno
for obj in (instance, DynamicFileno()):
assert select.select([obj], [], [], 0) == ([obj], [], [])
assert_raises(TypeError, select.select, [IndexOnly()], [], [], 0)
instance.fileno = IndexOnly
assert_raises(TypeError, select.select, [instance], [], [], 0)
finally:
lookup_reader.close()
lookup_writer.close()