Calls the close method on all registered plugins concurrently. If this manager was constructed with `skip_closing_plugins=True`, this method is a no-op so plugins owned by another component (e.g. a parent `Runner`) are not torn down while still in use. Raises: RuntimeError: I
(self)
| 322 | return None |
| 323 | |
| 324 | async def close(self) -> None: |
| 325 | """Calls the close method on all registered plugins concurrently. |
| 326 | |
| 327 | If this manager was constructed with `skip_closing_plugins=True`, this |
| 328 | method is a no-op so plugins owned by another component (e.g. a parent |
| 329 | `Runner`) are not torn down while still in use. |
| 330 | |
| 331 | Raises: |
| 332 | RuntimeError: If one or more plugins failed to close, containing |
| 333 | details of all failures. |
| 334 | """ |
| 335 | if self._skip_closing_plugins: |
| 336 | logger.debug( |
| 337 | "Skipping plugin close; plugins are owned by another component." |
| 338 | ) |
| 339 | return |
| 340 | exceptions = {} |
| 341 | # We iterate sequentially to avoid creating new tasks which can cause issues |
| 342 | # with some libraries (like anyio/mcp) that rely on task-local context. |
| 343 | for plugin in self.plugins: |
| 344 | try: |
| 345 | if sys.version_info >= (3, 11): |
| 346 | async with asyncio.timeout(self._close_timeout): |
| 347 | await plugin.close() |
| 348 | else: |
| 349 | # For Python < 3.11, we use wait_for which creates a new task. |
| 350 | # This might still cause issues with task-local contexts, but |
| 351 | # asyncio.timeout is not available. |
| 352 | await asyncio.wait_for(plugin.close(), timeout=self._close_timeout) |
| 353 | except Exception as e: |
| 354 | exceptions[plugin.name] = e |
| 355 | if isinstance(e, (asyncio.TimeoutError, asyncio.CancelledError)): |
| 356 | logger.warning( |
| 357 | "Timeout/Cancelled while closing plugin: %s", plugin.name |
| 358 | ) |
| 359 | else: |
| 360 | logger.error( |
| 361 | "Error during close of plugin %s: %s", |
| 362 | plugin.name, |
| 363 | e, |
| 364 | exc_info=e, |
| 365 | ) |
| 366 | |
| 367 | if exceptions: |
| 368 | error_summary = ", ".join( |
| 369 | f"'{name}': {type(exc).__name__}" for name, exc in exceptions.items() |
| 370 | ) |
| 371 | raise RuntimeError(f"Failed to close plugins: {error_summary}") |