mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
* Use ordinary fileno lookup in select Assisted-by: Codex:GPT-6 * Share select descriptor conversion Assisted-by: Codex:GPT-6
202 lines
5.6 KiB
Python
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()
|