utils.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import threading
  2. import signal
  3. import time
  4. import daemon
  5. from daemon import pidfile
  6. from .hands import *
  7. from .hands import __version__
  8. from .services.base import BaseService
  9. class ServicesUtil(object):
  10. def __init__(self, services, run_daemon=False, force_stop=False, stop_daemon=False):
  11. self._services = services
  12. self.run_daemon = run_daemon
  13. self.force_stop = force_stop
  14. self.stop_daemon = stop_daemon
  15. self.EXIT_EVENT = threading.Event()
  16. self.check_interval = 30
  17. self.files_preserve_map = {}
  18. def restart(self):
  19. self.stop()
  20. time.sleep(5)
  21. self.start_and_watch()
  22. def start_and_watch(self):
  23. logging.info(time.ctime())
  24. logging.info(f'MaxKB version {__version__}, more see https://www.maxkb.cn')
  25. self.start()
  26. if self.run_daemon:
  27. self.show_status()
  28. with self.daemon_context:
  29. self.watch()
  30. else:
  31. self.watch()
  32. def start(self):
  33. for service in self._services:
  34. service: BaseService
  35. service.start()
  36. self.files_preserve_map[service.name] = service.log_file
  37. time.sleep(1)
  38. def stop(self):
  39. for service in self._services:
  40. service: BaseService
  41. service.stop(force=self.force_stop)
  42. if self.stop_daemon:
  43. self._stop_daemon()
  44. # -- watch --
  45. def watch(self):
  46. while not self.EXIT_EVENT.is_set():
  47. try:
  48. _exit = self._watch()
  49. if _exit:
  50. break
  51. time.sleep(self.check_interval)
  52. except KeyboardInterrupt:
  53. print('Start stop services')
  54. break
  55. self.clean_up()
  56. def _watch(self):
  57. for service in self._services:
  58. service: BaseService
  59. service.watch()
  60. if service.EXIT_EVENT.is_set():
  61. self.EXIT_EVENT.set()
  62. return True
  63. return False
  64. # -- end watch --
  65. def clean_up(self):
  66. if not self.EXIT_EVENT.is_set():
  67. self.EXIT_EVENT.set()
  68. self.stop()
  69. def show_status(self):
  70. for service in self._services:
  71. service: BaseService
  72. service.show_status()
  73. # -- daemon --
  74. def _stop_daemon(self):
  75. if self.daemon_pid and self.daemon_is_running:
  76. os.kill(self.daemon_pid, 15)
  77. self.remove_daemon_pid()
  78. def remove_daemon_pid(self):
  79. if os.path.isfile(self.daemon_pid_filepath):
  80. os.unlink(self.daemon_pid_filepath)
  81. @property
  82. def daemon_pid(self):
  83. if not os.path.isfile(self.daemon_pid_filepath):
  84. return 0
  85. with open(self.daemon_pid_filepath) as f:
  86. try:
  87. pid = int(f.read().strip())
  88. except ValueError:
  89. pid = 0
  90. return pid
  91. @property
  92. def daemon_is_running(self):
  93. try:
  94. os.kill(self.daemon_pid, 0)
  95. except (OSError, ProcessLookupError):
  96. return False
  97. else:
  98. return True
  99. @property
  100. def daemon_pid_filepath(self):
  101. return os.path.join(TMP_DIR, 'mk.pid')
  102. @property
  103. def daemon_log_filepath(self):
  104. return os.path.join(LOG_DIR, 'mk.log')
  105. @property
  106. def daemon_context(self):
  107. daemon_log_file = open(self.daemon_log_filepath, 'a')
  108. context = daemon.DaemonContext(
  109. pidfile=pidfile.TimeoutPIDLockFile(self.daemon_pid_filepath),
  110. signal_map={
  111. signal.SIGTERM: lambda x, y: self.clean_up(),
  112. signal.SIGHUP: 'terminate',
  113. },
  114. stdout=daemon_log_file,
  115. stderr=daemon_log_file,
  116. files_preserve=list(self.files_preserve_map.values()),
  117. detach_process=True,
  118. )
  119. return context
  120. # -- end daemon --