Skip to content

Commit

Permalink
vault backup: 2025-01-11 17:39:18
Browse files Browse the repository at this point in the history
  • Loading branch information
abhiaagarwal committed Jan 11, 2025
1 parent f536cf9 commit f35e067
Show file tree
Hide file tree
Showing 3 changed files with 8 additions and 5 deletions.
Empty file removed content/().md
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ if __name__ == "__main__":
asyncio.run(main())
```

is actually _deeply_ unsafe. Let me cite the (in)famous article that alerted me to this problem, [the heisenberg lurking in your async code](https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/). Here's also an excellent [stack overflow](https://stackoverflow.com/a/76823668/21551208) answer that goes a bit more in depth. In short, due to python's garbage collector, those `task_*` objects we created are weak references. Python's garbage collector doesn't understand that those `task_*` objects have a life after the `asyncio.gather`, and they may just be arbitrarily garbage collected by python, and never run.
is _deeply_ unsafe. Let me cite the (in)famous article that alerted me to this problem, [the Heisenbug lurking in your async code](https://textual.textualize.io/blog/2023/02/11/the-heisenbug-lurking-in-your-async-code/). Here's also an excellent [stack overflow](https://stackoverflow.com/a/76823668/21551208) answer that goes a bit more in depth. In short, due to python's garbage collector, those `task_*` objects we created are weak references. Python's garbage collector doesn't understand that those `task_*` objects have a life after the `asyncio.gather`, and they may just be arbitrarily garbage collected by python, and never run.

Why does python do this? ¯\\\_(ツ)\_/¯. I would like to have a cordial conversation to whoever designed it this way.

In fact, the [asyncio docs for `create_task`](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) have a warning for this:

![[running-a-bunch-of-futures-asyncio-docs.png]]Alright, fair, but I, like hundreds of millions developers, will skip text that's in a grey box. It needs to have red scary text, maybe outlined in red, and it should also have a popup in the browser. Guido Van Rossum should mail each IP address that has ever downloaded python a hand-written letter warning them of this. That's how serious this problem is.
![[running-a-bunch-of-futures-asyncio-docs.png]]

Alright, fair, but I, like hundreds of millions developers, will skip text that's in a grey box. It needs to have red scary text, maybe outlined in red, and it should also have a popup in the browser. Guido Van Rossum should mail each IP address that has ever downloaded python a hand-written letter warning them of this. That's how serious this problem is.

The alternative solution, as the docs mention, is to use `asyncio.TaskGroup`. I actually _love_ the `asyncio.TaskGroup()` abstraction, and it serves its purpose well.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: The only python interview question you will ever need
tags:
- thoughts
---
I've been thinking recently about Python API design (as one does, in their mid 20s). I'm someone who cares deeply writing performant code, so I often turn to [[threading|`threading, multiprocessing`]] or `asyncio` when dealing with IO-bound work (which is the majority of python applications).
I've been thinking recently about Python API design (as one does, in their mid 20s). I'm someone who cares deeply writing performant code, so I often turn to [[threading|threading, multiprocessing]] or asyncio when dealing with IO-bound work (which is the majority of python applications).

I was looking at the API design for `concurrent.futures.ProcessPoolExecutor/ThreadPoolExecutor.submit()`, which looks something like:

Expand All @@ -16,7 +16,7 @@ with ProcessPoolExecutor() as executor:
task = executor.submit(cpu_bound_function, 5, keyword_arg=10)
```

Versus `asyncio.TaskGroup.create_task()`, which looks something like:
And `asyncio.TaskGroup.create_task()`, which looks something like:

```python
async def io_bound_function(val, *, keyword_arg):
Expand All @@ -34,7 +34,7 @@ If it isn't obvious to you: time to study :)

# Appendix: What LLMs say

This isn't a particularly esoteric question or anything, but I wanted to see what LLMs think about it. Here's what a few of them say.
This isn't a particularly esoteric question or anything, but I wanted to see what LLMs think about it. The prompt was exactly the text above, with the first and last paragraphs excluded. Here's what a few of them say.

> [!example]- Claude 3.5 Sonnet
>
Expand Down Expand Up @@ -152,6 +152,7 @@ This isn't a particularly esoteric question or anything, but I wanted to see wha
> > [!info] Verdict
> > A bit wordy, but overall, the right answer. o1 in general is a bit wordy (not sure if this is intentional behavior), but it could be cut in half and still be right.
> [!example]- Gemini 1206 Exp
> You've hit upon a subtle but important distinction between synchronous and asynchronous programming models, and how that impacts API design. Here's a breakdown of why `concurrent.futures` and `asyncio` differ in their task submission approaches:
>
Expand Down

0 comments on commit f35e067

Please sign in to comment.