| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 532b9db commit eb09595
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -573,6 +573,13 @@ def _loop_reading(self, fut=None): | |||
| 573 | 573 | self.max_size) | |
| 574 | 574 | except OSError as exc: | |
| 575 | 575 | self._protocol.error_received(exc) | |
| 576 | + if not self._closing and not self._conn_lost: | ||
| 577 | + # Some errors are transient and recoverable, e.g. a | ||
| 578 | + # ConnectionResetError raised synchronously by WSARecvFrom() | ||
| 579 | + # from a stale ICMP port-unreachable notification on a UDP | ||
| 580 | + # socket. Re-arm the read loop instead of leaving it dead | ||
| 581 | + # (gh-127057). | ||
| 582 | + self._loop.call_soon(self._loop_reading) | ||
| 576 | 583 | except exceptions.CancelledError: | |
| 577 | 584 | if not self._closing: | |
| 578 | 585 | raise | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1583,6 +1583,71 @@ def create_socket(): | |||
| 1583 | 1583 | transport_1.close() | |
| 1584 | 1584 | transport_2.close() | |
| 1585 | 1585 | ||
| 1586 | + def test_datagram_recvfrom_connection_reset_recovers(self): | ||
| 1587 | + # gh-127057: on Windows, a UDP socket that previously sent a | ||
| 1588 | + # datagram to an address that wasn't listening can raise | ||
| 1589 | + # ConnectionResetError (WSAECONNRESET) on a later receive | ||
| 1590 | + # attempt: synchronously from WSARecvFrom() on ProactorEventLoop | ||
| 1591 | + # (instead of via the completion result already handled in | ||
| 1592 | + # _finish_recvfrom() for gh-91227), or from a plain recvfrom() | ||
| 1593 | + # on SelectorEventLoop. Either way the transport must keep | ||
| 1594 | + # working afterwards instead of the read loop dying silently. | ||
| 1595 | + loop = self.loop | ||
| 1596 | + | ||
| 1597 | + class Protocol(asyncio.DatagramProtocol): | ||
| 1598 | + def connection_made(self, transport): | ||
| 1599 | + self.transport = transport | ||
| 1600 | + self.errors = [] | ||
| 1601 | + self.received = [] | ||
| 1602 | + self.datagram_received_event = loop.create_future() | ||
| 1603 | + | ||
| 1604 | + def error_received(self, exc): | ||
| 1605 | + self.errors.append(exc) | ||
| 1606 | + | ||
| 1607 | + def datagram_received(self, data, addr): | ||
| 1608 | + self.received.append(data) | ||
| 1609 | + if not self.datagram_received_event.done(): | ||
| 1610 | + self.datagram_received_event.set_result(None) | ||
| 1611 | + | ||
| 1612 | + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 1613 | + sock.setblocking(False) | ||
| 1614 | + sock.bind(('127.0.0.1', 0)) | ||
| 1615 | + addr = sock.getsockname() | ||
| 1616 | + | ||
| 1617 | + # Bind and immediately close a second socket to get an address | ||
| 1618 | + # that is guaranteed not to be listening. | ||
| 1619 | + closed = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 1620 | + closed.bind(('127.0.0.1', 0)) | ||
| 1621 | + closed_addr = closed.getsockname() | ||
| 1622 | + closed.close() | ||
| 1623 | + | ||
| 1624 | + # Trigger a real ICMP port-unreachable now, before the socket is | ||
| 1625 | + # wrapped in a transport and before any read is armed for it -- | ||
| 1626 | + # on Windows this is what makes a Proactor's first WSARecvFrom() | ||
| 1627 | + # call raise synchronously. | ||
| 1628 | + sock.sendto(b'x', closed_addr) | ||
| 1629 | + | ||
| 1630 | + transport, protocol = loop.run_until_complete( | ||
| 1631 | + loop.create_datagram_endpoint(Protocol, sock=sock)) | ||
| 1632 | + | ||
| 1633 | + # The transport must still be able to receive afterwards -- this | ||
| 1634 | + # is the actual regression check, and must hold regardless of | ||
| 1635 | + # whether this platform surfaced an error for the bad send above. | ||
| 1636 | + transport.sendto(b'ping', addr) | ||
| 1637 | + loop.run_until_complete( | ||
| 1638 | + asyncio.wait_for(protocol.datagram_received_event, 10)) | ||
| 1639 | + self.assertEqual(protocol.received, [b'ping']) | ||
| 1640 | + | ||
| 1641 | + if sys.platform == 'win32': | ||
| 1642 | + # Windows reliably reports the bad send via a later recvfrom(); | ||
| 1643 | + # other platforms generally don't deliver ICMP errors to a | ||
| 1644 | + # plain recv() on an unconnected UDP socket. | ||
| 1645 | + self.assertEqual(len(protocol.errors), 1) | ||
| 1646 | + self.assertIsInstance(protocol.errors[0], ConnectionResetError) | ||
| 1647 | + | ||
| 1648 | + transport.close() | ||
| 1649 | + test_utils.run_briefly(loop) | ||
| 1650 | + | ||
| 1586 | 1651 | def test_internal_fds(self): | |
| 1587 | 1652 | loop = self.create_event_loop() | |
| 1588 | 1653 | if not isinstance(loop, selector_events.BaseSelectorEventLoop): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,6 @@ | |||
| 1 | + Fix :class:`asyncio.ProactorEventLoop` UDP transports so a | ||
| 2 | + :exc:`ConnectionResetError` raised synchronously from ``WSARecvFrom`` | ||
| 3 | + (reported when the same socket was previously used to send to an | ||
| 4 | + address that isn't listening) no longer breaks the read loop. This | ||
| 5 | + complements the existing handling of the equivalent asynchronous | ||
| 6 | + ``ERROR_PORT_UNREACHABLE`` completion result. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments