code: def graceful_exit(servers, *, loop,
                    signals=frozenset({signal.SIGINT, signal.SIGTERM})):
      
      signals = set(signals)
      flag = []
      for sig_num in signals:
          loop.add_signal_handler(sig_num, _exit_handler, sig_num, servers, flag)
      try:
          yield
      finally:
          for sig_num in signals:
              loop.remove_signal_handler(sig_num)
docs: Utility context-manager to help properly shutdown server in response to
      the OS signals
  
      By default this context-manager handles ``SIGINT`` and ``SIGTERM`` signals.
  
      There are two stages:
  
        1. first received signal closes servers
        2. subsequent signals raise ``SystemExit`` exception
  
      Example:
  
      .. code-block:: python3
  
          async def main(...):
              ...
              with graceful_exit([server], loop=loop):
                  await server.start(host, port)
                  print('Serving on {}:{}'.format(host, port))
                  await server.wait_closed()
                  print('Server closed')
  
      First stage calls ``server.close()`` and ``await server.wait_closed()``
      should complete successfully without errors. If server wasn't started yet,
      second stage runs to prevent server start.
  
      Second stage raises ``SystemExit`` exception, but you will receive
      ``asyncio.CancelledError`` in your ``async def main()`` coroutine. You
      can use ``try..finally`` constructs and context-managers to properly handle
      this error.
  
      This context-manager is designed to work in cooperation with
      :py:func:`python:asyncio.run` function:
  
      .. code-block:: python3
  
          if __name__ == '__main__':
              asyncio.run(main())
  
      :param servers: list of servers
      :param loop: asyncio-compatible event loop
      :param signals: set of the OS signals to handle