routes.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import collections
  2. import inspect
  3. from ray.dashboard.optional_deps import aiohttp
  4. from ray.dashboard.routes import BaseRouteTable
  5. from ray.dashboard.subprocesses.handle import SubprocessModuleHandle
  6. from ray.dashboard.subprocesses.utils import ResponseType
  7. class SubprocessRouteTable(BaseRouteTable):
  8. """
  9. A route table to bind http route to SubprocessModuleHandle and SubprocessModule.
  10. This class is used in cls object: all the decorator methods are @classmethod, and
  11. the routes are binded to the cls object.
  12. Note we have 2 handlers:
  13. 1. the child side handler, that is `handler` that contains real logic and
  14. is executed in the child process. It's added with __route_method__ and
  15. __route_path__ attributes. The child process runs a standalone aiohttp
  16. server.
  17. 2. the parent side handler, that just sends the request to the
  18. SubprocessModuleHandle at cls._bind_map[method][path].instance.
  19. With modifications:
  20. - __route_method__ and __route_path__ are added to both side's handlers.
  21. - method and path are added to self._bind_map.
  22. Lifecycle of a request:
  23. 1. Parent receives an aiohttp request.
  24. 2. Router finds by [method][path] and calls parent_side_handler.
  25. 3. `parent_side_handler` bookkeeps the request with a Future and sends a
  26. request to the subprocess.
  27. 4. Subprocesses receives the response and sends it back to the parent.
  28. 5. Parent responds to the aiohttp request with the response from the subprocess.
  29. """
  30. _bind_map = collections.defaultdict(dict)
  31. _routes = aiohttp.web.RouteTableDef()
  32. @classmethod
  33. def bind(cls, instance: "SubprocessModuleHandle"):
  34. # __route_method__ and __route_path__ are added to SubprocessModule's methods,
  35. # not the SubprocessModuleHandle's methods.
  36. def predicate(o):
  37. if inspect.isfunction(o):
  38. return hasattr(o, "__route_method__") and hasattr(o, "__route_path__")
  39. return False
  40. handler_routes = inspect.getmembers(instance.module_cls, predicate)
  41. for _, h in handler_routes:
  42. cls._bind_map[h.__route_method__][h.__route_path__].instance = instance
  43. @classmethod
  44. def _register_route(
  45. cls, method, path, resp_type: ResponseType = ResponseType.HTTP, **kwargs
  46. ):
  47. """
  48. Register a route to the module and return the decorated handler.
  49. """
  50. def _wrapper(handler):
  51. if path in cls._bind_map[method]:
  52. bind_info = cls._bind_map[method][path]
  53. raise Exception(
  54. f"Duplicated route path: {path}, "
  55. f"previous one registered at "
  56. f"{bind_info.filename}:{bind_info.lineno}"
  57. )
  58. bind_info = cls._BindInfo(
  59. handler.__code__.co_filename, handler.__code__.co_firstlineno, None
  60. )
  61. cls._bind_map[method][path] = bind_info
  62. async def parent_side_handler(
  63. request: aiohttp.web.Request,
  64. ) -> aiohttp.web.Response:
  65. bind_info = cls._bind_map[method][path]
  66. subprocess_module_handle = bind_info.instance
  67. return await subprocess_module_handle.proxy_request(request, resp_type)
  68. # Used in bind().
  69. handler.__route_method__ = method
  70. handler.__route_path__ = path
  71. # Used in bound_routes().
  72. parent_side_handler.__route_method__ = method
  73. parent_side_handler.__route_path__ = path
  74. parent_side_handler.__name__ = handler.__name__
  75. cls._routes.route(method, path)(parent_side_handler)
  76. return handler
  77. return _wrapper