/lib/server_base.py

https://github.com/LIMXTEC/electrumx · Python · 126 lines · 63 code · 19 blank · 44 comment · 8 complexity · 4ad5659fe0edf69a837d6eea140044a6 MD5 · raw file

  1. # Copyright (c) 2017, Neil Booth
  2. #
  3. # All rights reserved.
  4. #
  5. # See the file "LICENCE" for information about the copyright
  6. # and warranty status of this software.
  7. import asyncio
  8. import logging
  9. import os
  10. import signal
  11. import sys
  12. import time
  13. from functools import partial
  14. class ServerBase(object):
  15. '''Base class server implementation.
  16. Derived classes are expected to:
  17. - set PYTHON_MIN_VERSION and SUPPRESS_MESSAGES as appropriate
  18. - implement the start_servers() coroutine, called from the run() method.
  19. Upon return the event loop runs until the shutdown signal is received.
  20. - implement the shutdown() coroutine
  21. '''
  22. SUPPRESS_MESSAGES = [
  23. 'Fatal read error on socket transport',
  24. 'Fatal write error on socket transport',
  25. ]
  26. PYTHON_MIN_VERSION = (3, 6)
  27. def __init__(self, env):
  28. '''Save the environment, perform basic sanity checks, and set the
  29. event loop policy.
  30. '''
  31. self.logger = logging.getLogger(self.__class__.__name__)
  32. self.env = env
  33. # Sanity checks
  34. if sys.version_info < self.PYTHON_MIN_VERSION:
  35. mvs = '.'.join(str(part) for part in self.PYTHON_MIN_VERSION)
  36. raise RuntimeError('Python version >= {} is required'.format(mvs))
  37. if os.geteuid() == 0 and not env.allow_root:
  38. raise RuntimeError('RUNNING AS ROOT IS STRONGLY DISCOURAGED!\n'
  39. 'You shoud create an unprivileged user account '
  40. 'and use that.\n'
  41. 'To continue as root anyway, restart with '
  42. 'environment variable ALLOW_ROOT non-empty')
  43. # First asyncio operation must be to set the event loop policy
  44. # as this replaces the event loop
  45. asyncio.set_event_loop_policy(self.env.loop_policy)
  46. # Trigger this event to cleanly shutdown
  47. self.shutdown_event = asyncio.Event()
  48. async def start_servers(self):
  49. '''Override to perform initialization that requires the event loop,
  50. and start servers.'''
  51. pass
  52. async def shutdown(self):
  53. '''Override to perform the shutdown sequence, if any.'''
  54. pass
  55. async def _wait_for_shutdown_event(self):
  56. '''Wait for shutdown to be signalled, and log it.
  57. Derived classes may want to provide a shutdown() coroutine.'''
  58. # Shut down cleanly after waiting for shutdown to be signalled
  59. await self.shutdown_event.wait()
  60. self.logger.info('shutting down')
  61. # Wait for the shutdown sequence
  62. await self.shutdown()
  63. # Finally, work around an apparent asyncio bug that causes log
  64. # spew on shutdown for partially opened SSL sockets
  65. try:
  66. del asyncio.sslproto._SSLProtocolTransport.__del__
  67. except Exception:
  68. pass
  69. self.logger.info('shutdown complete')
  70. def on_signal(self, signame):
  71. '''Call on receipt of a signal to cleanly shutdown.'''
  72. self.logger.warning('received {} signal, initiating shutdown'
  73. .format(signame))
  74. self.shutdown_event.set()
  75. def on_exception(self, loop, context):
  76. '''Suppress spurious messages it appears we cannot control.'''
  77. message = context.get('message')
  78. if message in self.SUPPRESS_MESSAGES:
  79. return
  80. if 'accept_connection2()' in repr(context.get('task')):
  81. return
  82. loop.default_exception_handler(context)
  83. def run(self):
  84. '''Run the server application:
  85. - record start time
  86. - set the event loop policy as specified by the environment
  87. - install SIGINT and SIGKILL handlers to trigger shutdown_event
  88. - set loop's exception handler to suppress unwanted messages
  89. - run the event loop until start_servers() completes
  90. - run the event loop until shutdown is signalled
  91. '''
  92. self.start_time = time.time()
  93. loop = asyncio.get_event_loop()
  94. for signame in ('SIGINT', 'SIGTERM'):
  95. loop.add_signal_handler(getattr(signal, signame),
  96. partial(self.on_signal, signame))
  97. loop.set_exception_handler(self.on_exception)
  98. loop.run_until_complete(self.start_servers())
  99. loop.run_until_complete(self._wait_for_shutdown_event())
  100. loop.close()