services.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. """扩展服务的依赖顺序、共享实例和异步资源管理。"""
  2. from __future__ import annotations
  3. from contextlib import AsyncExitStack
  4. from dataclasses import dataclass
  5. import inspect
  6. import time
  7. from obs import get_logger
  8. logger = get_logger(__name__)
  9. from types import MappingProxyType
  10. from typing import Any, AsyncContextManager, Callable, Mapping
  11. @dataclass(frozen=True)
  12. class ServiceSpec:
  13. name: str
  14. factory: Callable[[Mapping[str, Any]], AsyncContextManager[Any]]
  15. requires: tuple[str, ...] = ()
  16. def ordered_names(dependencies: Mapping[str, set[str]], kind: str) -> list[str]:
  17. """稳定拓扑排序:无依赖的节点按名称排序,缺失引用和循环立即报错。"""
  18. pending = {name: set(refs) for name, refs in dependencies.items()}
  19. for name, refs in pending.items():
  20. missing = refs - pending.keys()
  21. if missing:
  22. raise ValueError(f"{kind} {name} 引用未注册项:{sorted(missing)}")
  23. result = []
  24. while pending:
  25. ready = sorted(name for name, refs in pending.items() if not refs)
  26. if not ready:
  27. raise ValueError(f"{kind}存在循环依赖:{sorted(pending)}")
  28. for name in ready:
  29. result.append(name)
  30. del pending[name]
  31. for refs in pending.values():
  32. refs.difference_update(ready)
  33. return result
  34. _active: ServiceContainer | None = None
  35. class ServiceContainer:
  36. """由 RuntimeEnvironment 管理;工厂必须返回异步上下文管理器。
  37. 工厂只收到其声明的依赖。资源先按依赖顺序创建,再逆序关闭。
  38. """
  39. def __init__(self, specs: Mapping[str, ServiceSpec]):
  40. self._specs = dict(specs)
  41. self._values: dict[str, Any] = {}
  42. self._stack = AsyncExitStack()
  43. async def start(self) -> None:
  44. global _active
  45. if _active is not None:
  46. raise RuntimeError("扩展服务已启动,请复用 RuntimeEnvironment")
  47. order = ordered_names({name: set(spec.requires) for name, spec in self._specs.items()}, "服务")
  48. for name in order:
  49. spec = self._specs[name]
  50. started = time.perf_counter()
  51. logger.debug("扩展服务启动", extra={"kv": {"event": "service.starting", "service": name}})
  52. dependencies = self.view(spec.requires)
  53. context = spec.factory(dependencies)
  54. if not hasattr(context, "__aenter__") or not hasattr(context, "__aexit__"):
  55. if inspect.iscoroutine(context):
  56. context.close()
  57. raise TypeError(f"服务 {name} 的工厂必须返回异步上下文管理器")
  58. self._values[name] = await self._stack.enter_async_context(context)
  59. logger.info("扩展服务就绪", extra={"kv": {"event": "service.ready", "service": name,
  60. "duration_ms": round((time.perf_counter() - started) * 1000, 1)}})
  61. _active = self
  62. def view(self, names) -> Mapping[str, Any]:
  63. try:
  64. return MappingProxyType({name: self._values[name] for name in names})
  65. except KeyError as exc:
  66. raise RuntimeError(f"扩展服务 {exc.args[0]} 尚未就绪,请在 RuntimeEnvironment 内使用") from exc
  67. async def close(self) -> None:
  68. global _active
  69. try:
  70. await self._stack.aclose()
  71. finally:
  72. self._values.clear()
  73. if _active is self:
  74. _active = None
  75. def service_view(names) -> Mapping[str, Any]:
  76. if not names:
  77. return MappingProxyType({})
  78. if _active is None:
  79. raise RuntimeError("扩展服务尚未启动,请在 RuntimeEnvironment 内使用")
  80. return _active.view(names)
  81. def get_service(name: str) -> Any:
  82. """工具在执行时获取服务,不要在 import 或 setup 时调用。"""
  83. return service_view((name,))[name]