feat: add SIGHUP hot config reload
On SIGHUP, re-read the YAML config file and update mutable runtime settings: timeout, retries, log_level, and pool config (sources, intervals, thresholds). Pool triggers an immediate source re-fetch. Listen address and chain require restart.
This commit is contained in:
@@ -62,6 +62,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
args = _parse_args(argv)
|
||||
|
||||
config = load_config(args.config) if args.config else Config()
|
||||
if args.config:
|
||||
config.config_file = args.config
|
||||
|
||||
if args.listen:
|
||||
if ":" in args.listen:
|
||||
|
||||
@@ -74,6 +74,7 @@ class Config:
|
||||
log_level: str = "info"
|
||||
proxy_source: ProxySourceConfig | None = None
|
||||
proxy_pool: ProxyPoolConfig | None = None
|
||||
config_file: str = ""
|
||||
|
||||
|
||||
def parse_proxy_url(url: str) -> ChainHop:
|
||||
|
||||
@@ -90,6 +90,13 @@ class ProxyPool:
|
||||
self._tasks.append(asyncio.create_task(self._refresh_loop()))
|
||||
self._tasks.append(asyncio.create_task(self._health_loop()))
|
||||
|
||||
async def reload(self, cfg: ProxyPoolConfig) -> None:
|
||||
"""Update pool config and trigger source re-fetch."""
|
||||
self._cfg = cfg
|
||||
logger.info("pool: config reloaded, re-fetching sources")
|
||||
await self._fetch_all_sources()
|
||||
self._save_state()
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Cancel background tasks and save state."""
|
||||
self._stop.set()
|
||||
|
||||
@@ -8,7 +8,7 @@ import signal
|
||||
import struct
|
||||
import time
|
||||
|
||||
from .config import Config
|
||||
from .config import Config, load_config
|
||||
from .metrics import Metrics
|
||||
from .pool import ProxyPool
|
||||
from .proto import ProtoError, Socks5Reply, build_chain, read_socks5_address
|
||||
@@ -253,6 +253,32 @@ async def serve(config: Config) -> None:
|
||||
for sig in (signal.SIGTERM, signal.SIGINT):
|
||||
loop.add_signal_handler(sig, lambda s=sig: stop.set_result(s))
|
||||
|
||||
# SIGHUP: hot-reload config (timeout, retries, log_level, pool settings)
|
||||
async def _reload() -> None:
|
||||
if not config.config_file:
|
||||
logger.warning("reload: no config file specified, ignoring SIGHUP")
|
||||
return
|
||||
try:
|
||||
new = load_config(config.config_file)
|
||||
except Exception as e:
|
||||
logger.warning("reload: failed to read config: %s", e)
|
||||
return
|
||||
config.timeout = new.timeout
|
||||
config.retries = new.retries
|
||||
if new.log_level != config.log_level:
|
||||
config.log_level = new.log_level
|
||||
logging.getLogger("s5p").setLevel(
|
||||
getattr(logging, new.log_level.upper(), logging.INFO),
|
||||
)
|
||||
if isinstance(proxy_pool, ProxyPool) and new.proxy_pool:
|
||||
await proxy_pool.reload(new.proxy_pool)
|
||||
logger.info("reload: config reloaded")
|
||||
|
||||
def _on_sighup() -> None:
|
||||
asyncio.ensure_future(_reload())
|
||||
|
||||
loop.add_signal_handler(signal.SIGHUP, _on_sighup)
|
||||
|
||||
metrics_stop = asyncio.Event()
|
||||
pool_ref = proxy_pool if isinstance(proxy_pool, ProxyPool) else None
|
||||
metrics_task = asyncio.create_task(_metrics_logger(metrics, metrics_stop, pool_ref))
|
||||
|
||||
Reference in New Issue
Block a user