Skip to content

Commit 0eec1f3

Browse files
committed
Fire and forget async ctx blocks anti-pattern
1 parent 2ce3fa4 commit 0eec1f3

File tree

1 file changed

+33
-0
lines changed

1 file changed

+33
-0
lines changed

antipatterns.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,36 @@ class User:
164164
Replacing `assert` statements with `raise AssertionError(...)` (or whatever
165165
exception class you prefer) ensures that these checks cannot be trivially
166166
disabled.
167+
168+
169+
## Fire and forget async context blocks
170+
171+
When writing asyncio-based async context blocks (i.e. using async context managers), we sometimes
172+
fail to continuously check that the background task started is still running. For example, the
173+
following is how our Connection service was implemented
174+
(https://github.com/ethereum/trinity/blob/0db2a36706e5327fa040258bb5fef3fae75d9d8c/p2p/connection.py#L132-L153)
175+
and since it doesn't perform any health checks on the multiplexing task running in the background,
176+
a crash in the multiplexer that failed to cancel the connection would leave the connection running
177+
forever.
178+
179+
```python
180+
async def run():
181+
async with self._multiplexer.multiplex():
182+
...
183+
await self.manager.wait_finished()
184+
```
185+
186+
In order to avoid this we need to make sure our async context managers always yield a reference
187+
to something that can be used to wait for (or check the state) of the background task. In the
188+
above example it could be something like:
189+
190+
```python
191+
async def run():
192+
async with self._multiplexer.multiplex() as multiplexing_task:
193+
...
194+
await asyncio.wait(
195+
[self.manager.wait_finished(), multiplexing_task],
196+
return_when=asyncio.FIST_COMPLETED)
197+
if multiplexing_task.done():
198+
self.manager.cancel()
199+
```

0 commit comments

Comments
 (0)