/docs/userguide/services.rst

https://github.com/ask/mode · ReStructuredText · 249 lines · 168 code · 81 blank · 0 comment · 0 complexity · 003c4fe1090cdcf518bf07f401fcdcdb MD5 · raw file

  1. .. _guide-services:
  2. ==================
  3. Services
  4. ==================
  5. .. module:: mode
  6. :noindex:
  7. .. currentmodule:: mode
  8. .. contents::
  9. :local:
  10. :depth: 1
  11. Basics
  12. ======
  13. The Service class manages the services and background tasks started
  14. by the async program, so that we can implement graceful shutdown
  15. and also helps us visualize the relationships between
  16. services in a dependency graph.
  17. Anything that can be started/stopped and restarted
  18. should probably be a subclass of the :class:`Service` class.
  19. The Service API
  20. ===============
  21. A service can be started, and it may start other services
  22. and background tasks. Most actions in a service are asynchronous, so needs
  23. to be executed from within an async function.
  24. This first section defines the public service API, as if used by the user,
  25. the next section will define the methods service authors write to define new
  26. services.
  27. Methods
  28. -------
  29. .. class:: Service
  30. :noindex:
  31. .. automethod:: start
  32. :noindex:
  33. .. automethod:: maybe_start
  34. :noindex:
  35. .. automethod:: stop
  36. :noindex:
  37. .. automethod:: restart
  38. :noindex:
  39. .. automethod:: wait_until_stopped
  40. :noindex:
  41. .. automethod:: set_shutdown
  42. :noindex:
  43. Attributes
  44. ----------
  45. .. class:: Service
  46. :noindex:
  47. .. autoattribute:: started
  48. :noindex:
  49. .. autoattribute:: label
  50. :noindex:
  51. .. autoattribute:: shortlabel
  52. :noindex:
  53. .. autoattribute:: beacon
  54. :noindex:
  55. Defining new services
  56. =====================
  57. Adding child services
  58. ---------------------
  59. Child services can be added in three ways,
  60. 1) Using ``add_dependency()`` in ``__post_init__``:
  61. .. sourcecode:: python
  62. class MyService(Service):
  63. def __post_init__(self) -> None:
  64. self.add_dependency(OtherService())
  65. 2) Using ``add_dependency()`` in ``on_start``:
  66. .. sourcecode:: python
  67. class MyService(Service):
  68. async def on_start(self) -> None:
  69. self.add_dependency(OtherService())
  70. 3) Using ``on_init_dependencies()``
  71. This is is a method that if customized should return an iterable
  72. of service instances:
  73. .. sourcecode:: python
  74. from typing import Iterable
  75. from mode import Service, ServiceT
  76. class MyService(Service):
  77. def on_init_dependencies(self) -> Iterable[ServiceT]:
  78. return [ServiceA(), ServiceB()]
  79. Ordering
  80. --------
  81. Knowing exactly what is called, when it's called and in what order
  82. is important, and this table will help you understand that:
  83. Order at start (``await Service.start()``)
  84. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  85. 1. The ``on_first_start`` callback is called.
  86. 2. Service logs: ``"[Service] Starting..."``.
  87. 3. ``on_start`` callback is called.
  88. 4. All ``@Service.task`` background tasks are started (in definition order).
  89. 5. All child services added by ``add_dependency()``, or
  90. ``on_init_dependencies())`` are started.
  91. 6. Service logs: ``"[Service] Started"``.
  92. 7. The ``on_started`` callback is called.
  93. Order when stopping (``await Service.stop()``)
  94. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  95. 1. Service logs; ``"[Service] Stopping..."``.
  96. 2. The ``on_stop()`` callback is called.
  97. 3. All child services are stopped, in reverse order.
  98. 4. All asyncio futures added by ``add_future()`` are cancelled
  99. in reverse order.
  100. 5. Service logs: ``"[Service] Stopped"``.
  101. 6. If ``Service.wait_for_shutdown = True``, it will wait for the
  102. ``Service.set_shutdown()`` signal to be called.
  103. 7. All futures started by ``add_future()`` will be gathered (awaited).
  104. 8. The ``on_shutdown()`` callback is called.
  105. 9. The service logs: ``"[Service] Shutdown complete!"``.
  106. Order when restarting (``await Service.restart()``)
  107. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  108. 1. The service is stopped (``await service.stop()``).
  109. 2. The ``__post_init__()`` callback is called again.
  110. 3. The service is started (``await service.start()``).
  111. Callbacks
  112. ---------
  113. .. class:: Service
  114. :noindex:
  115. .. automethod:: on_start
  116. :noindex:
  117. .. automethod:: on_first_start
  118. :noindex:
  119. .. automethod:: on_started
  120. :noindex:
  121. .. automethod:: on_stop
  122. :noindex:
  123. .. automethod:: on_shutdown
  124. :noindex:
  125. .. automethod:: on_restart
  126. :noindex:
  127. Handling Errors
  128. ---------------
  129. .. class:: Service
  130. :noindex:
  131. .. automethod:: crash
  132. :noindex:
  133. Utilities
  134. ---------
  135. .. class:: Service
  136. :noindex:
  137. .. automethod:: sleep
  138. :noindex:
  139. .. automethod:: wait
  140. :noindex:
  141. Logging
  142. -------
  143. Your service may add logging to notify the user what is going on, and the
  144. Service class includes some shortcuts to include the service name etc. in
  145. logs.
  146. The ``self.log`` delegate contains shortcuts for logging:
  147. .. sourcecode:: python
  148. # examples/logging.py
  149. from mode import Service
  150. class MyService(Service):
  151. async def on_start(self) -> None:
  152. self.log.debug('This is a debug message')
  153. self.log.info('This is a info message')
  154. self.log.warn('This is a warning message')
  155. self.log.error('This is a error message')
  156. self.log.exception('This is a error message with traceback')
  157. self.log.critical('This is a critical message')
  158. self.log.debug('I can also include templates: %r %d %s',
  159. [1, 2, 3], 303, 'string')
  160. The logs will be emitted by a logger with the same name as the module the
  161. Service class is defined in. It's similar to this setup, that you can do
  162. if you want to manually define the logger used by the service:
  163. .. sourcecode:: python
  164. # examples/manual_service_logger.py
  165. from mode import Service, get_logger
  166. logger = get_logger(__name__)
  167. class MyService(Service):
  168. logger = logger