.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
background.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2# pylint: disable=missing-module-docstring, cyclic-import
3
4import json
5import time
6import threading
7import os
8import signal
9from typing import Any, Dict, List, Literal, Optional, Tuple, TypedDict, Union
10
11import redis.exceptions
12
13from searx import logger, settings, searx_debug
14from searx.redisdb import client as get_redis_client
15from searx.exceptions import SearxSettingsException
16from searx.search.processors import PROCESSORS
17from searx.search.checker import Checker
18from searx.search.checker.scheduler import scheduler_function
19
20
21REDIS_RESULT_KEY = 'SearXNG_checker_result'
22REDIS_LOCK_KEY = 'SearXNG_checker_lock'
23
24
25CheckerResult = Union['CheckerOk', 'CheckerErr', 'CheckerOther']
26
27
28class CheckerOk(TypedDict):
29 """Checking the engines succeeded"""
30
31 status: Literal['ok']
32 engines: Dict[str, 'EngineResult']
33 timestamp: int
34
35
36class CheckerErr(TypedDict):
37 """Checking the engines failed"""
38
39 status: Literal['error']
40 timestamp: int
41
42
43class CheckerOther(TypedDict):
44 """The status is unknown or disabled"""
45
46 status: Literal['unknown', 'disabled']
47
48
49EngineResult = Union['EngineOk', 'EngineErr']
50
51
52class EngineOk(TypedDict):
53 """Checking the engine succeeded"""
54
55 success: Literal[True]
56
57
58class EngineErr(TypedDict):
59 """Checking the engine failed"""
60
61 success: Literal[False]
62 errors: Dict[str, List[str]]
63
64
65def _get_interval(every: Any, error_msg: str) -> Tuple[int, int]:
66 if isinstance(every, int):
67 return (every, every)
68
69 if (
70 not isinstance(every, (tuple, list))
71 or len(every) != 2 # type: ignore
72 or not isinstance(every[0], int)
73 or not isinstance(every[1], int)
74 ):
75 raise SearxSettingsException(error_msg, None)
76 return (every[0], every[1])
77
78
79def get_result() -> CheckerResult:
80 client = get_redis_client()
81 if client is None:
82 # without Redis, the checker is disabled
83 return {'status': 'disabled'}
84 serialized_result: Optional[bytes] = client.get(REDIS_RESULT_KEY)
85 if serialized_result is None:
86 # the Redis key does not exist
87 return {'status': 'unknown'}
88 return json.loads(serialized_result)
89
90
91def _set_result(result: CheckerResult):
92 client = get_redis_client()
93 if client is None:
94 # without Redis, the function does nothing
95 return
96 client.set(REDIS_RESULT_KEY, json.dumps(result))
97
98
100 return int(time.time() / 3600) * 3600
101
102
103def run():
104 try:
105 # use a Redis lock to make sure there is no checker running at the same time
106 # (this should not happen, this is a safety measure)
107 with get_redis_client().lock(REDIS_LOCK_KEY, blocking_timeout=60, timeout=3600):
108 logger.info('Starting checker')
109 result: CheckerOk = {'status': 'ok', 'engines': {}, 'timestamp': _timestamp()}
110 for name, processor in PROCESSORS.items():
111 logger.debug('Checking %s engine', name)
112 checker = Checker(processor)
113 checker.run()
114 if checker.test_results.successful:
115 result['engines'][name] = {'success': True}
116 else:
117 result['engines'][name] = {'success': False, 'errors': checker.test_results.errors}
118
119 _set_result(result)
120 logger.info('Check done')
121 except redis.exceptions.LockError:
122 _set_result({'status': 'error', 'timestamp': _timestamp()})
123 logger.exception('Error while running the checker')
124 except Exception: # pylint: disable=broad-except
125 _set_result({'status': 'error', 'timestamp': _timestamp()})
126 logger.exception('Error while running the checker')
127
128
129def _signal_handler(_signum: int, _frame: Any):
130 t = threading.Thread(target=run)
131 t.daemon = True
132 t.start()
133
134
136 if hasattr(signal, 'SIGUSR1'):
137 # Windows doesn't support SIGUSR1
138 logger.info('Send SIGUSR1 signal to pid %i to start the checker', os.getpid())
139 signal.signal(signal.SIGUSR1, _signal_handler)
140
141 # special case when debug is activate
142 if searx_debug and settings['checker']['off_when_debug']:
143 logger.info('debug mode: checker is disabled')
144 return
145
146 # check value of checker.scheduling.every now
147 scheduling = settings['checker']['scheduling']
148 if scheduling is None or not scheduling:
149 logger.info('Checker scheduler is disabled')
150 return
151
152 # make sure there is a Redis connection
153 if get_redis_client() is None:
154 logger.error('The checker requires Redis')
155 return
156
157 # start the background scheduler
158 every_range = _get_interval(scheduling.get('every', (300, 1800)), 'checker.scheduling.every is not a int or list')
159 start_after_range = _get_interval(
160 scheduling.get('start_after', (300, 1800)), 'checker.scheduling.start_after is not a int or list'
161 )
162 t = threading.Thread(
163 target=scheduler_function,
164 args=(start_after_range[0], start_after_range[1], every_range[0], every_range[1], run),
165 name='checker_scheduler',
166 )
167 t.daemon = True
168 t.start()
_set_result(CheckerResult result)
Definition background.py:91
Tuple[int, int] _get_interval(Any every, str error_msg)
Definition background.py:65
_signal_handler(int _signum, Any _frame)