MCPcopy
hub / github.com/python-trio/trio / finalize_remaining

Method finalize_remaining

src/trio/_core/_asyncgens.py:160–218  ·  view source on GitHub ↗
(self, runner: _run.Runner)

Source from the content-addressed store, hash-verified

158 sys.set_asyncgen_hooks(firstiter=firstiter, finalizer=finalizer) # type: ignore[arg-type] # Finalizer doesn't use AsyncGeneratorType
159
160 async def finalize_remaining(self, runner: _run.Runner) -> None:
161 # This is called from init after shutting down the system nursery.
162 # The only tasks running at this point are init and
163 # the run_sync_soon task, and since the system nursery is closed,
164 # there's no way for user code to spawn more.
165 assert _core.current_task() is runner.init_task
166 assert len(runner.tasks) == 2
167
168 # To make async generator finalization easier to reason
169 # about, we'll shut down asyncgen garbage collection by turning
170 # the alive WeakSet into a regular set.
171 self.alive = set(self.alive)
172
173 # Process all pending run_sync_soon callbacks, in case one of
174 # them was an asyncgen finalizer that snuck in under the wire.
175 runner.entry_queue.run_sync_soon(runner.reschedule, runner.init_task)
176 await _core.wait_task_rescheduled(
177 lambda _: _core.Abort.FAILED, # pragma: no cover
178 )
179 self.alive.update(self.trailing_needs_finalize)
180 self.trailing_needs_finalize.clear()
181
182 # None of the still-living tasks use async generators, so
183 # every async generator must be suspended at a yield point --
184 # there's no one to be doing the iteration. That's good,
185 # because aclose() only works on an asyncgen that's suspended
186 # at a yield point. (If it's suspended at an event loop trap,
187 # because someone is in the middle of iterating it, then you
188 # get a RuntimeError on 3.8+, and a nasty surprise on earlier
189 # versions due to https://bugs.python.org/issue32526.)
190 #
191 # However, once we start aclose() of one async generator, it
192 # might start fetching the next value from another, thus
193 # preventing us from closing that other (at least until
194 # aclose() of the first one is complete). This constraint
195 # effectively requires us to finalize the remaining asyncgens
196 # in arbitrary order, rather than doing all of them at the
197 # same time. On 3.8+ we could defer any generator with
198 # ag_running=True to a later batch, but that only catches
199 # the case where our aclose() starts after the user's
200 # asend()/etc. If our aclose() starts first, then the
201 # user's asend()/etc will raise RuntimeError, since they're
202 # probably not checking ag_running.
203 #
204 # It might be possible to allow some parallelized cleanup if
205 # we can determine that a certain set of asyncgens have no
206 # interdependencies, using gc.get_referents() and such.
207 # But just doing one at a time will typically work well enough
208 # (since each aclose() executes in a cancelled scope) and
209 # is much easier to reason about.
210
211 # It's possible that that cleanup code will itself create
212 # more async generators, so we iterate repeatedly until
213 # all are gone.
214 while self.alive:
215 batch = self.alive
216 self.alive = _ASYNC_GEN_SET()
217 for agen in batch:

Callers 1

initMethod · 0.80

Calls 3

_finalize_oneMethod · 0.95
name_asyncgenFunction · 0.85
run_sync_soonMethod · 0.45

Tested by

no test coverage detected