fix socketpair EOF

This commit is contained in:
Flowseal committed 2026-10-10 18:54:09 +03:00
1 parent a232472cbe
commit a0a92f5e7e
6 files changed
+396 -4

No files matched your search

+7 -3
View File
@@ -525,10 +525,14 @@ class _HttpChannel:
async def _recover(self) -> None:
while not self.closed:
wakeup = asyncio.create_task(self.recovery_wakeup.wait())
try:
await asyncio.wait_for(self.recovery_wakeup.wait(), REPLAY_CHECK_SECONDS)
except asyncio.TimeoutError:
pass
# wait_for can swallow cancellation when the wakeup completes
# at the same time on Python 3.8-3.11 (CPython #86296).
await asyncio.wait((wakeup,), timeout=REPLAY_CHECK_SECONDS)
finally:
wakeup.cancel()
await asyncio.gather(wakeup, return_exceptions=True)
self.recovery_wakeup.clear()
now = time.monotonic()
self.lane.log_wait(now)
+84
View File
@@ -0,0 +1,84 @@
import asyncio
import logging
import signal
import socket
import sys
import threading
log = logging.getLogger('tg-mtproto-proxy')
def _replace_self_pipe(loop):
reader, writer = socket.socketpair()
try:
reader.setblocking(False)
writer.setblocking(False)
if threading.current_thread() is threading.main_thread():
previous_fd = signal.set_wakeup_fd(writer.fileno())
if previous_fd != loop._csock.fileno():
signal.set_wakeup_fd(previous_fd)
except BaseException:
reader.close()
writer.close()
raise
old_reader, old_writer = loop._ssock, loop._csock
loop._ssock, loop._csock = reader, writer
old_reader.close()
old_writer.close()
class _SelfPipeGuard:
"""Work around https://github.com/python/cpython/issues/156333"""
def __init__(self, loop):
self.loop = loop
self.read_self_pipe = loop._loop_self_reading
def __call__(self, future=None):
loop = self.loop
if future is not None:
if loop._self_reading_future is not future:
return
try:
data = future.result()
except OSError as exc:
reason = str(exc)
except BaseException:
return self.read_self_pipe(future)
else:
if data:
return self.read_self_pipe(future)
reason = 'EOF'
loop._self_reading_future = None
self._recover(reason)
return
return self.read_self_pipe(future)
def _recover(self, reason):
loop = self.loop
if loop.is_closed() or loop._self_reading_future is not None:
return
try:
_replace_self_pipe(loop)
except OSError as exc:
log.warning('Windows event loop wakeup socket recovery failed: %s; '
'retry in 1s', exc)
loop.call_later(1, self._recover, reason)
return
log.warning('Windows event loop wakeup socket lost (%s); recreated', reason)
self.read_self_pipe()
def install_self_pipe_guard(loop):
if sys.platform != 'win32' or not isinstance(loop, asyncio.ProactorEventLoop):
return
if not isinstance(loop._loop_self_reading, _SelfPipeGuard):
guard = _SelfPipeGuard(loop)
loop._loop_self_reading = guard
future = loop._self_reading_future
if future is not None:
future.remove_done_callback(guard.read_self_pipe)
future.add_done_callback(guard)
+2
View File
@@ -29,6 +29,7 @@ from .balancer import balancer
from .pool import ws_pool, cf_worker_pool
from .cf_h2 import CfH2Pool
from .network_debug import log_ws_flow
from .event_loop import install_self_pipe_guard
from ._aes import Cipher, algorithms, modes
@@ -360,6 +361,7 @@ _client_tasks: Set[asyncio.Task] = set()
async def _run(stop_event: Optional[asyncio.Event] = None):
global _server_instance, _server_stop_event, cf_h2_pool
install_self_pipe_guard(asyncio.get_running_loop())
_server_stop_event = stop_event
reset_tcp_backoff()