Skip to content

Commit e418436

Browse files
committed
More notes
1 parent 5e338d1 commit e418436

File tree

3 files changed

+49
-10
lines changed

3 files changed

+49
-10
lines changed

misc_info.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# What?
2+
3+
This is just some assorted info that may be useful to anyone considering why
4+
certain things in this library are done the way there are and also
5+
as a place to record some important information that I may not always remember
6+
all of, but may need to revisit in the future related to it.
7+
8+
## Task switching semantic changes in the standard library
9+
10+
### Before eager tasks
11+
12+
- await
13+
- async with (both enter and exit)
14+
- async for (per loop)
15+
- return (from a coroutine)
16+
- yield (from a coroutine)
17+
- Raising an exception (that leaves a coroutine)
18+
19+
### As of eager tasks, the below are included
20+
21+
- Constructing an asyncio.Task with eager_start=True while the associated event loop is running
22+
- asyncio.create_task
23+
- asyncio.ensure_future (when wrapping a non-future awaitable)
24+
- asyncio.wait_for\* (when any non-future awaitables are waited on) (ensure_future)
25+
- asyncio.as_completed\* (when any non-future awaitables are waited on) (ensure_future)
26+
- asyncio.gather\* (when any non-future awaitables are gathered) (ensure_future)
27+
- asyncio.IocpProactor.accept (unlikely to impact most) (ensure_future on a coro)
28+
- asyncio.StreamReaderProtocol.connection_made (library use, perhaps?) (create task)
29+
- Constructing asyncio.BaseSubprocessTransport (create task)
30+
- Note: extremely unlikely to impact anyone, asyncio.create_subprocess_* doesn't construct this prior to an await.
31+
32+
\* Likely not a meaningfully new place, awaiting these immediately is the most common use.

src/async_utils/_nm_building_blocks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ def __init__(self, lock: threading.RLock) -> None:
8080
async def __aenter__(self) -> None:
8181
acquired = False
8282
while not acquired:
83+
# TODO: This kindof sucks, and we can avoid this
84+
# if we're also the scheduler, find a way not to use this in
85+
# any async scheduler that is threading-aware when I get to that.
8386
acquired = self._lock.acquire(blocking=False)
8487
await asyncio.sleep(0)
8588

src/async_utils/waterfall.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ async def _loop(self) -> None:
127127

128128
t = asyncio.create_task(self.callback(queue_items))
129129
tasks.add(t)
130-
t.add_done_callback(tasks.remove)
130+
t.add_done_callback(tasks.discard)
131131

132132
for _ in range(num_items):
133133
self.queue.task_done()
@@ -137,7 +137,10 @@ async def _loop(self) -> None:
137137
await asyncio.wait_for(f, timeout=self.max_wait_finalize)
138138

139139
async def _finalize(self) -> None:
140-
# WARNING: Do not allow an async context switch before the gather below
140+
# WARNING: Do not allow an async context switch before the queue is drained
141+
# This can be changed to utilize queue.Shutdown in 3.13+
142+
# or when more of asyncio queues have been replaced here
143+
# as part of freethreading efforts.
141144

142145
self._alive = False
143146
remaining_items: Sequence[T] = []
@@ -152,27 +155,28 @@ async def _finalize(self) -> None:
152155

153156
remaining_items.append(ev)
154157

158+
# Context switches are safe again.
159+
155160
if not remaining_items:
156161
return
157162

158163
num_remaining = len(remaining_items)
159164

160-
pending_futures: list[asyncio.Task[object]] = []
165+
remaining_tasks: set[asyncio.Task[object]] = set()
161166

162167
for chunk in (
163168
remaining_items[p : p + self.max_quantity]
164169
for p in range(0, num_remaining, self.max_quantity)
165170
):
166171
fut = asyncio.create_task(self.callback(chunk))
167-
pending_futures.append(fut)
172+
fut.add_done_callback(remaining_tasks.discard)
173+
remaining_tasks.add(fut)
168174

169-
gathered = asyncio.gather(*pending_futures)
175+
timeout = self.max_wait_finalize
176+
_done, pending = await asyncio.wait(remaining_tasks, timeout=timeout)
170177

171-
try:
172-
await asyncio.wait_for(gathered, timeout=self.max_wait_finalize)
173-
except TimeoutError:
174-
for task in pending_futures:
175-
task.cancel()
178+
for task in pending:
179+
task.cancel()
176180

177181
for _ in range(num_remaining):
178182
self.queue.task_done()

0 commit comments

Comments
 (0)