Skip to content

Commit 35276b6

Browse files
committed
Ensure we hold strong references to tasks
see python/cpython#88831
1 parent f99db35 commit 35276b6

File tree

2 files changed

+29
-3
lines changed

2 files changed

+29
-3
lines changed

aioesphomeapi/connection.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def __init__(
9696
) -> None:
9797
self._params = params
9898
self.on_stop: Optional[Callable[[], Coroutine[Any, Any, None]]] = on_stop
99+
self._on_stop_task: Optional[asyncio.Task[None]] = None
99100
self._socket: Optional[socket.socket] = None
100101
self._frame_helper: Optional[APIFrameHelper] = None
101102
self._api_version: Optional[APIVersion] = None
@@ -142,6 +143,10 @@ def _cleanup(self) -> None:
142143
self._connect_task.cancel()
143144
self._connect_task = None
144145

146+
if self._keep_alive_task is not None:
147+
self._keep_alive_task.cancel()
148+
self._keep_alive_task = None
149+
145150
if self._frame_helper is not None:
146151
self._frame_helper.close()
147152
self._frame_helper = None
@@ -151,8 +156,19 @@ def _cleanup(self) -> None:
151156
self._socket = None
152157

153158
if self.on_stop and self._connect_complete:
159+
160+
def _remove_on_stop_task():
161+
"""Remove the stop task from the reconnect loop.
162+
163+
We need to do this because the asyncio does not hold
164+
a strong reference to the task, so it can be garbage
165+
collected unexpectedly.
166+
"""
167+
self._on_stop_task = None
168+
154169
# Ensure on_stop is called only once
155-
asyncio.create_task(self.on_stop())
170+
self._on_stop_task = asyncio.create_task(self.on_stop())
171+
self._on_stop_task.add_done_callback(_remove_on_stop_task)
156172
self.on_stop = None
157173

158174
# Note: we don't explicitly cancel the ping/read task here
@@ -318,7 +334,7 @@ async def _keep_alive_loop() -> None:
318334
self._report_fatal_error(err)
319335
return
320336

321-
asyncio.create_task(_keep_alive_loop())
337+
self._keep_alive_task = asyncio.create_task(_keep_alive_loop())
322338

323339
async def connect(self, *, login: bool) -> None:
324340
if self._connection_state != ConnectionState.INITIALIZED:

aioesphomeapi/reconnect_logic.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,17 @@ async def stop(self) -> None:
200200
await self._stop_zc_listen()
201201

202202
def stop_callback(self) -> None:
203-
asyncio.create_task(self.stop())
203+
def _remove_stop_task() -> None:
204+
"""Remove the stop task from the reconnect loop.
205+
206+
We need to do this because the asyncio does not hold
207+
a strong reference to the task, so it can be garbage
208+
collected unexpectedly.
209+
"""
210+
self._stop_task = None
211+
212+
self._stop_task = asyncio.create_task(self.stop())
213+
self._stop_task.add_done_callback(_remove_stop_task)
204214

205215
async def _start_zc_listen(self) -> None:
206216
"""Listen for mDNS records.

0 commit comments

Comments
 (0)