background.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. # lint: pylint
  3. # pylint: disable=missing-module-docstring, missing-function-docstring
  4. import json
  5. import random
  6. import time
  7. import threading
  8. import os
  9. import signal
  10. from searx import logger, settings, searx_debug
  11. from searx.exceptions import SearxSettingsException
  12. from searx.search.processors import PROCESSORS
  13. from searx.search.checker import Checker
  14. from searx.shared import schedule, storage
  15. CHECKER_RESULT = 'CHECKER_RESULT'
  16. running = threading.Lock()
  17. def _get_interval(every, error_msg):
  18. if isinstance(every, int):
  19. every = (every, every)
  20. if not isinstance(every, (tuple, list))\
  21. or len(every) != 2\
  22. or not isinstance(every[0], int)\
  23. or not isinstance(every[1], int):
  24. raise SearxSettingsException(error_msg, None)
  25. return every
  26. def _get_every():
  27. every = settings.get('checker', {}).get('scheduling', {}).get('every', (300, 1800))
  28. return _get_interval(every, 'checker.scheduling.every is not a int or list')
  29. def get_result():
  30. serialized_result = storage.get_str(CHECKER_RESULT)
  31. if serialized_result is not None:
  32. return json.loads(serialized_result)
  33. return {'status': 'unknown'}
  34. def _set_result(result, include_timestamp=True):
  35. if include_timestamp:
  36. result['timestamp'] = int(time.time() / 3600) * 3600
  37. storage.set_str(CHECKER_RESULT, json.dumps(result))
  38. def run():
  39. if not running.acquire(blocking=False): # pylint: disable=consider-using-with
  40. return
  41. try:
  42. logger.info('Starting checker')
  43. result = {
  44. 'status': 'ok',
  45. 'engines': {}
  46. }
  47. for name, processor in PROCESSORS.items():
  48. logger.debug('Checking %s engine', name)
  49. checker = Checker(processor)
  50. checker.run()
  51. if checker.test_results.succesfull:
  52. result['engines'][name] = {'success': True}
  53. else:
  54. result['engines'][name] = {'success': False, 'errors': checker.test_results.errors}
  55. _set_result(result)
  56. logger.info('Check done')
  57. except Exception: # pylint: disable=broad-except
  58. _set_result({'status': 'error'})
  59. logger.exception('Error while running the checker')
  60. finally:
  61. running.release()
  62. def _run_with_delay():
  63. every = _get_every()
  64. delay = random.randint(0, every[1] - every[0])
  65. logger.debug('Start checker in %i seconds', delay)
  66. time.sleep(delay)
  67. run()
  68. def _start_scheduling():
  69. every = _get_every()
  70. if schedule(every[0], _run_with_delay):
  71. run()
  72. def _signal_handler(_signum, _frame):
  73. t = threading.Thread(target=run)
  74. t.daemon = True
  75. t.start()
  76. def initialize():
  77. if hasattr(signal, 'SIGUSR1'):
  78. # Windows doesn't support SIGUSR1
  79. logger.info('Send SIGUSR1 signal to pid %i to start the checker', os.getpid())
  80. signal.signal(signal.SIGUSR1, _signal_handler)
  81. # disabled by default
  82. _set_result({'status': 'disabled'}, include_timestamp=False)
  83. # special case when debug is activate
  84. if searx_debug and settings.get('checker', {}).get('off_when_debug', True):
  85. logger.info('debug mode: checker is disabled')
  86. return
  87. # check value of checker.scheduling.every now
  88. scheduling = settings.get('checker', {}).get('scheduling', None)
  89. if scheduling is None or not scheduling:
  90. logger.info('Checker scheduler is disabled')
  91. return
  92. #
  93. _set_result({'status': 'unknown'}, include_timestamp=False)
  94. start_after = scheduling.get('start_after', (300, 1800))
  95. start_after = _get_interval(start_after, 'checker.scheduling.start_after is not a int or list')
  96. delay = random.randint(start_after[0], start_after[1])
  97. logger.info('Start checker in %i seconds', delay)
  98. t = threading.Timer(delay, _start_scheduling)
  99. t.daemon = True
  100. t.start()