fix: add timeouts to SSL/DNS blocking calls, reset stuck AI jobs on startup
- SSL handshake: set socket timeout before wrap_socket (prevents indefinite hang) - SSL executor: asyncio.wait_for(..., timeout=12) - DNS gethostbyname: asyncio.wait_for(..., timeout=6) - analyze_site: hard 90s timeout wrapper - _assess_one: hard 180s ceiling via asyncio.timeout() - ai_worker_loop: reset 'running' → 'pending' on startup (clears crashed-session jobs) - Add POST /api/ai/reset endpoint + UI button to unstick jobs without restart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -100,7 +100,9 @@ async def _get_hosting_info(domain: str) -> dict:
|
||||
"ip_country": None, "ip_region": None, "eu_hosted": None}
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
ip = await loop.run_in_executor(None, socket.gethostbyname, domain)
|
||||
ip = await asyncio.wait_for(
|
||||
loop.run_in_executor(None, socket.gethostbyname, domain), timeout=6
|
||||
)
|
||||
info["ip"] = ip
|
||||
async with httpx.AsyncClient(timeout=6) as client:
|
||||
r = await client.get(
|
||||
@@ -123,7 +125,7 @@ async def _get_hosting_info(domain: str) -> dict:
|
||||
return info
|
||||
|
||||
|
||||
async def analyze_site(domain: str) -> dict:
|
||||
async def _analyze_site_inner(domain: str) -> dict:
|
||||
result = {
|
||||
"domain": domain,
|
||||
"reachable": False, "load_time_ms": None, "status_code": None,
|
||||
@@ -361,13 +363,27 @@ async def analyze_site(domain: str) -> dict:
|
||||
import datetime as _dt
|
||||
ctx = _ssl.create_default_context()
|
||||
with socket.create_connection((domain, 443), timeout=5) as s:
|
||||
s.settimeout(5) # SSL handshake timeout (wrap_socket has no timeout arg)
|
||||
with ctx.wrap_socket(s, server_hostname=domain) as ss:
|
||||
cert = ss.getpeercert()
|
||||
exp = _dt.datetime.strptime(cert["notAfter"], "%b %d %H:%M:%S %Y %Z")
|
||||
return True, (_dt.datetime.utcnow() - exp).days * -1
|
||||
loop = asyncio.get_event_loop()
|
||||
result["ssl_valid"], result["ssl_expiry_days"] = await loop.run_in_executor(None, _ssl_check)
|
||||
result["ssl_valid"], result["ssl_expiry_days"] = await asyncio.wait_for(
|
||||
loop.run_in_executor(None, _ssl_check), timeout=12
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def analyze_site(domain: str) -> dict:
|
||||
"""Public entry point — hard 90s timeout so workers never hang permanently."""
|
||||
try:
|
||||
return await asyncio.wait_for(_analyze_site_inner(domain), timeout=90)
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("analyze_site timed out for %s", domain)
|
||||
return {"domain": domain, "reachable": False, "error": "analyze_site timeout",
|
||||
"emails": [], "phones": [], "whatsapp": [], "social_links": [],
|
||||
"kit_digital": False, "kit_digital_signals": []}
|
||||
|
||||
Reference in New Issue
Block a user