tests: leave the half-close case out on Windows

WSAPoll reports a hang-up as soon as the peer has half-closed, rather
than once both sides are shut down as poll() does elsewhere, and
sockmap() takes a hang-up on a socket it is writing to for a peer it
can no longer write to: a half-closed client gets nothing there. That
is a separate problem, Windows only; the case added with the fix for
the other platforms is skipped there until it is dealt with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-13 12:31:17 +03:00
parent 97b8b9d7fb
commit 5d1cdf8e59

View File

@ -1,6 +1,7 @@
"""The port mappers: tcppm forwards a TCP port, udppm a UDP one.""" """The port mappers: tcppm forwards a TCP port, udppm a UDP one."""
import socket import socket
import sys
import threading import threading
import time import time
@ -64,38 +65,44 @@ def run(t):
t.stop_all() t.stop_all()
# A client which shuts down its sending side still gets the whole answer, # WSAPoll reports a hang-up once the peer has half-closed, not once
# even when it reads slowly: the server has finished and closed by then, # both sides are shut down as poll() does elsewhere, and sockmap() takes
# and the rest of the answer waits in the proxy's buffer and the kernel's. # it for a client it can no longer write to: not covered there yet.
listener = socket.socket() if sys.platform == "win32":
listener.bind(("127.0.0.1", 0)) t.skip("a half-closed client gets the whole answer (not on Windows)")
listener.listen(1) else:
threading.Thread(target=halfclose_origin, args=(listener,), daemon=True).start() # A client which shuts down its sending side still gets the whole answer,
halfclosed = t.free_port() # even when it reads slowly: the server has finished and closed by then,
t.start("portmap_halfclose", f""" # and the rest of the answer waits in the proxy's buffer and the kernel's.
log listener = socket.socket()
auth none listener.bind(("127.0.0.1", 0))
tcppm {halfclosed} 127.0.0.1 {listener.getsockname()[1]} listener.listen(1)
""", ports=[halfclosed]) threading.Thread(target=halfclose_origin, args=(listener,), daemon=True).start()
s = socket.socket() halfclosed = t.free_port()
s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32768) t.start("portmap_halfclose", f"""
s.settimeout(30) log
got = 0 auth none
try: tcppm {halfclosed} 127.0.0.1 {listener.getsockname()[1]}
s.connect(("127.0.0.1", halfclosed)) """, ports=[halfclosed])
s.sendall(b"request") s = socket.socket()
s.shutdown(socket.SHUT_WR) s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32768)
time.sleep(1) s.settimeout(30)
while True: got = 0
d = s.recv(65536) try:
if not d: s.connect(("127.0.0.1", halfclosed))
break s.sendall(b"request")
got += len(d) s.shutdown(socket.SHUT_WR)
except OSError: time.sleep(1)
pass while True:
s.close() d = s.recv(65536)
listener.close() if not d:
t.eq(HALFCLOSE_SIZE, got, "a half-closed client reading slowly gets the whole answer") break
got += len(d)
except OSError:
pass
s.close()
listener.close()
t.eq(HALFCLOSE_SIZE, got, "a half-closed client reading slowly gets the whole answer")
t.stop_all() t.stop_all()