PageRenderTime 28ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/server_base.py

https://bitbucket.org/arfonzo/electrumx4egc
Python | 128 lines | 64 code | 20 blank | 44 comment | 8 complexity | 341d90b31a4326281e12356ef7ffd43a 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 os
  9. import signal
  10. import sys
  11. import time
  12. from functools import partial
  13. import lib.util as util
  14. class ServerBase(util.LoggedClass):
  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. super().__init__()
  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. self.logger.info('event loop policy: {}'.format(self.env.loop_policy))
  46. asyncio.set_event_loop_policy(self.env.loop_policy)
  47. # Trigger this event to cleanly shutdown
  48. self.shutdown_event = asyncio.Event()
  49. async def start_servers(self):
  50. '''Override to perform initialization that requires the event loop,
  51. and start servers.'''
  52. pass
  53. async def shutdown(self):
  54. '''Override to perform the shutdown sequence, if any.'''
  55. pass
  56. async def _wait_for_shutdown_event(self):
  57. '''Wait for shutdown to be signalled, and log it.
  58. Derived classes may want to provide a shutdown() coroutine.'''
  59. # Shut down cleanly after waiting for shutdown to be signalled
  60. await self.shutdown_event.wait()
  61. self.logger.info('shutting down')
  62. # Wait for the shutdown sequence
  63. await self.shutdown()
  64. # Finally, work around an apparent asyncio bug that causes log
  65. # spew on shutdown for partially opened SSL sockets
  66. try:
  67. del asyncio.sslproto._SSLProtocolTransport.__del__
  68. except Exception:
  69. pass
  70. self.logger.info('shutdown complete')
  71. def on_signal(self, signame):
  72. '''Call on receipt of a signal to cleanly shutdown.'''
  73. self.logger.warning('received {} signal, initiating shutdown'
  74. .format(signame))
  75. self.shutdown_event.set()
  76. def on_exception(self, loop, context):
  77. '''Suppress spurious messages it appears we cannot control.'''
  78. message = context.get('message')
  79. if message in self.SUPPRESS_MESSAGES:
  80. return
  81. if 'accept_connection2()' in repr(context.get('task')):
  82. return
  83. loop.default_exception_handler(context)
  84. def run(self):
  85. '''Run the server application:
  86. - record start time
  87. - set the event loop policy as specified by the environment
  88. - install SIGINT and SIGKILL handlers to trigger shutdown_event
  89. - set loop's exception handler to suppress unwanted messages
  90. - run the event loop until start_servers() completes
  91. - run the event loop until shutdown is signalled
  92. '''
  93. self.start_time = time.time()
  94. loop = asyncio.get_event_loop()
  95. for signame in ('SIGINT', 'SIGTERM'):
  96. loop.add_signal_handler(getattr(signal, signame),
  97. partial(self.on_signal, signame))
  98. loop.set_exception_handler(self.on_exception)
  99. loop.run_until_complete(self.start_servers())
  100. loop.run_until_complete(self._wait_for_shutdown_event())
  101. loop.close()