_subprocess.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """
  2. Some light wrappers around Python's multiprocessing, to deal with cleanly
  3. starting child processes.
  4. """
  5. from __future__ import annotations
  6. import multiprocessing
  7. import os
  8. import sys
  9. from multiprocessing.context import SpawnProcess
  10. from socket import socket
  11. from typing import Callable
  12. from uvicorn.config import Config
  13. multiprocessing.allow_connection_pickling()
  14. spawn = multiprocessing.get_context("spawn")
  15. def get_subprocess(
  16. config: Config,
  17. target: Callable[..., None],
  18. sockets: list[socket],
  19. ) -> SpawnProcess:
  20. """
  21. Called in the parent process, to instantiate a new child process instance.
  22. The child is not yet started at this point.
  23. * config - The Uvicorn configuration instance.
  24. * target - A callable that accepts a list of sockets. In practice this will
  25. be the `Server.run()` method.
  26. * sockets - A list of sockets to pass to the server. Sockets are bound once
  27. by the parent process, and then passed to the child processes.
  28. """
  29. # We pass across the stdin fileno, and reopen it in the child process.
  30. # This is required for some debugging environments.
  31. try:
  32. stdin_fileno = sys.stdin.fileno()
  33. # The `sys.stdin` can be `None`, see https://docs.python.org/3/library/sys.html#sys.__stdin__.
  34. except (AttributeError, OSError):
  35. stdin_fileno = None
  36. kwargs = {
  37. "config": config,
  38. "target": target,
  39. "sockets": sockets,
  40. "stdin_fileno": stdin_fileno,
  41. }
  42. return spawn.Process(target=subprocess_started, kwargs=kwargs)
  43. def subprocess_started(
  44. config: Config,
  45. target: Callable[..., None],
  46. sockets: list[socket],
  47. stdin_fileno: int | None,
  48. ) -> None:
  49. """
  50. Called when the child process starts.
  51. * config - The Uvicorn configuration instance.
  52. * target - A callable that accepts a list of sockets. In practice this will
  53. be the `Server.run()` method.
  54. * sockets - A list of sockets to pass to the server. Sockets are bound once
  55. by the parent process, and then passed to the child processes.
  56. * stdin_fileno - The file number of sys.stdin, so that it can be reattached
  57. to the child process.
  58. """
  59. # Re-open stdin.
  60. if stdin_fileno is not None:
  61. sys.stdin = os.fdopen(stdin_fileno) # pragma: full coverage
  62. # Logging needs to be setup again for each child.
  63. config.configure_logging()
  64. try:
  65. # Now we can call into `Server.run(sockets=sockets)`
  66. target(sockets=sockets)
  67. except KeyboardInterrupt: # pragma: no cover
  68. # supress the exception to avoid a traceback from subprocess.Popen
  69. # the parent already expects us to end, so no vital information is lost
  70. pass