diff --git a/python/loopmini/loop.py b/python/loopmini/loop.py index e1607b8..bd9a273 100644 --- a/python/loopmini/loop.py +++ b/python/loopmini/loop.py @@ -180,7 +180,13 @@ def _remove_writer(self, fd): return self.remove_writer(fd) sock_connect,_sock_connect,_sock_connect_cb = _SelLoop.sock_connect,_SelLoop._sock_connect,_SelLoop._sock_connect_cb def _make_socket_transport(self, sock, protocol, waiter=None, *, extra=None, server=None, context=None): - return SockTransport(self, sock, protocol, waiter, extra, server, context) + # The accept path (gh: _accept_connection2) swallows transport-creation errors + # outside debug mode; report before re-raising so server-side bugs are never silent + try: return SockTransport(self, sock, protocol, waiter, extra, server, context) + except (SystemExit, KeyboardInterrupt): raise + except BaseException as e: + if server is not None: self.call_exception_handler(dict(message='loopmini: error creating server-side transport', exception=e, socket=sock)) + raise def _make_ssl_transport(self, sock, protocol, sslcontext, waiter=None, *, server_side=False, server_hostname=None, extra=None, server=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, call_connection_made=True, context=None): diff --git a/python/loopmini/transports.py b/python/loopmini/transports.py index 0234a40..03d4597 100644 --- a/python/loopmini/transports.py +++ b/python/loopmini/transports.py @@ -1,5 +1,6 @@ "Socket transports for the Rust-reactor loop, feeding stock asyncio protocols." -import asyncio, socket +import asyncio, socket, sys +_server_takes_transport = sys.version_info >= (3, 13) # Server._attach/_detach gained the transport arg in 3.13 (gh-113538) from asyncio import constants, futures, transports class _TransportLifecycle: @@ -40,7 +41,7 @@ def __init__(self, loop, sock, protocol, waiter=None, extra=None, server=None, c except OSError: pass self._buffer = bytearray() self._closing = self._reading = self._eof = self._lost = False - if server is not None: server._attach(self) + if server is not None: server._attach(self) if _server_takes_transport else server._attach() loop.call_soon(protocol.connection_made, self, context=context) loop.call_soon(self.resume_reading, context=context) if waiter is not None: loop.call_soon(futures._set_result_unless_cancelled, waiter, None) @@ -137,7 +138,7 @@ def _force_close(self, exc): def _close_resource(self): self._sock.close() if self._server is not None: - self._server._detach(self) + self._server._detach(self) if _server_takes_transport else self._server._detach() self._server = None class DatagramTransport(_TransportLifecycle, asyncio.DatagramTransport): diff --git a/tests/test_loop.py b/tests/test_loop.py index f05997b..0ab153a 100644 --- a/tests/test_loop.py +++ b/tests/test_loop.py @@ -2,7 +2,8 @@ import asyncio, contextvars, ctypes, os, pytest, random, signal, socket, sys, threading, time import loopmini -def run(coro): return asyncio.run(coro, loop_factory=loopmini.new_event_loop) +def run(coro): + with asyncio.Runner(loop_factory=loopmini.new_event_loop) as r: return r.run(coro) def test_cancelled_timer_released(): """A cancelled timer must leave the reactor's timer map at once.