mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-09-14 09:46:39 +00:00
Merge pull request #101 from joeyleake/fix/95-serial-fd-leak-on-timeout
Fix serial fd leak in SerialConnection.connect() on timeout
This commit is contained in:
@@ -71,14 +71,24 @@ class SerialConnection:
|
|||||||
self._connected_event.clear()
|
self._connected_event.clear()
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
await serial_asyncio.create_serial_connection(
|
transport, _ = await serial_asyncio.create_serial_connection(
|
||||||
loop,
|
loop,
|
||||||
lambda: self.MCSerialClientProtocol(self),
|
lambda: self.MCSerialClientProtocol(self),
|
||||||
self.port,
|
self.port,
|
||||||
baudrate=self.baudrate,
|
baudrate=self.baudrate,
|
||||||
)
|
)
|
||||||
|
|
||||||
await asyncio.wait_for(self._connected_event.wait(), timeout=timeout)
|
try:
|
||||||
|
await asyncio.wait_for(self._connected_event.wait(), timeout=timeout)
|
||||||
|
except Exception:
|
||||||
|
# create_serial_connection() already opened the port's fds;
|
||||||
|
# connection_made() never fired (or didn't in time) to hand them
|
||||||
|
# to self.transport, so close directly on the local reference or
|
||||||
|
# they leak until the process runs out of fds (#95).
|
||||||
|
if self.transport is transport:
|
||||||
|
self.transport = None
|
||||||
|
transport.close()
|
||||||
|
raise
|
||||||
logger.info("Serial Connection started")
|
logger.info("Serial Connection started")
|
||||||
return self.port
|
return self.port
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -29,3 +30,28 @@ async def test_handle_rx_discards_leading_junk_before_frame_start():
|
|||||||
assert conn.header == b""
|
assert conn.header == b""
|
||||||
assert conn.inframe == b""
|
assert conn.inframe == b""
|
||||||
assert conn.frame_expected_size == 0
|
assert conn.frame_expected_size == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_connect_closes_transport_on_timeout():
|
||||||
|
"""Regression for #95: if connection_made() never fires before the
|
||||||
|
connect() timeout, the fds create_serial_connection() already opened
|
||||||
|
must not leak -- connect() should close the transport before raising."""
|
||||||
|
conn = SerialConnection("/dev/null", 115200)
|
||||||
|
|
||||||
|
mock_transport = MagicMock()
|
||||||
|
mock_protocol = MagicMock()
|
||||||
|
|
||||||
|
async def fake_create_serial_connection(loop, protocol_factory, port, baudrate):
|
||||||
|
# Never call connection_made(), so _connected_event stays unset.
|
||||||
|
return mock_transport, mock_protocol
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"meshcore.serial_cx.serial_asyncio.create_serial_connection",
|
||||||
|
side_effect=fake_create_serial_connection,
|
||||||
|
):
|
||||||
|
with pytest.raises(asyncio.TimeoutError):
|
||||||
|
await conn.connect(timeout=0.05)
|
||||||
|
|
||||||
|
mock_transport.close.assert_called_once()
|
||||||
|
assert conn.transport is None
|
||||||
|
|||||||
Reference in New Issue
Block a user