Commit e526a3c2 authored by Vitaly Lipatov's avatar Vitaly Lipatov

web-api: remove check lock, allow unlimited concurrent checks

All check operations use curl to /dev/null with -w for results, no temp files or shared state between requests. Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent c7c5569d
......@@ -58,7 +58,6 @@ CHECK_GATEWAYS = [
]
CHECK_TIMEOUT = 10
_check_lock = threading.Lock()
_list_lock = threading.Lock()
......@@ -1739,89 +1738,83 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
self.send_error_json(400, "Invalid domain or IP")
return
if not _check_lock.acquire(blocking=False):
self.send_error_json(429, "Check already in progress")
return
try:
self.log_message("CHECK %s%s", domain,
" file=%s" % file_url if file_url else "")
accept = self.headers.get("Accept", "")
if "text/event-stream" in accept:
# SSE stream (browser) — send results as they arrive
self.send_response(200)
self.send_header("Content-Type", "text/event-stream; charset=utf-8")
self.send_header("Cache-Control", "no-cache")
self.end_headers()
# Send check event immediately — no blocking
base = {"domain": domain}
if file_url:
base["file_url"] = file_url
self.send_sse("check", base)
if file_url:
# File URL mode: speed test only
def on_speed(gw_name, res):
self.send_sse("speed", {"gateway": gw_name, "result": res})
run_speed_test(file_url, on_speed)
else:
# Domain mode: gateways + throttle
url = "https://%s/" % domain
has_v6 = False
try:
socket.getaddrinfo(domain, None, socket.AF_INET6, socket.SOCK_STREAM)
has_v6 = True
except socket.gaierror:
pass
with concurrent.futures.ThreadPoolExecutor(max_workers=len(CHECK_GATEWAYS) * 2 + 2) as pool:
gw_futures = {}
for name, proxy_v4, proxy_v6 in CHECK_GATEWAYS:
f4 = pool.submit(_check_one, name, proxy_v4, url, "-4")
gw_futures[f4] = True
if has_v6 and proxy_v6:
f6 = pool.submit(_check_one, name, proxy_v6, url, "-6")
gw_futures[f6] = True
assets_future = pool.submit(_find_assets, CHECK_GATEWAYS[0][1], url)
for future in concurrent.futures.as_completed(gw_futures):
name, ipver, status, code = future.result()
self.send_sse("gateway", {
"name": name, "ipver": ipver,
"status": status, "http_code": code,
})
self.log_message("CHECK %s%s", domain,
" file=%s" % file_url if file_url else "")
assets = assets_future.result()
# Phase 2: throttle detection
if assets:
asset_url = assets[0]
with concurrent.futures.ThreadPoolExecutor(max_workers=len(CHECK_GATEWAYS)) as pool:
throttle_futures = [
pool.submit(_check_throttle, name, proxy, asset_url)
for name, proxy, _pv6 in CHECK_GATEWAYS
]
for future in concurrent.futures.as_completed(throttle_futures):
gw_name, result = future.result()
if result and result.get("throttled"):
self.send_sse("throttle", {
"gateway": gw_name, "result": result,
})
self.send_sse("done", {})
accept = self.headers.get("Accept", "")
if "text/event-stream" in accept:
# SSE stream (browser) — send results as they arrive
self.send_response(200)
self.send_header("Content-Type", "text/event-stream; charset=utf-8")
self.send_header("Cache-Control", "no-cache")
self.end_headers()
# Send check event immediately — no blocking
base = {"domain": domain}
if file_url:
base["file_url"] = file_url
self.send_sse("check", base)
if file_url:
# File URL mode: speed test only
def on_speed(gw_name, res):
self.send_sse("speed", {"gateway": gw_name, "result": res})
run_speed_test(file_url, on_speed)
else:
# JSON response (curl/scripts)
result = check_site(domain, file_url=file_url)
if file_url:
speed = {}
def on_speed(gw_name, res):
speed[gw_name] = res
run_speed_test(file_url, on_speed)
result["speed"] = speed
self.send_json(result)
finally:
_check_lock.release()
# Domain mode: gateways + throttle
url = "https://%s/" % domain
has_v6 = False
try:
socket.getaddrinfo(domain, None, socket.AF_INET6, socket.SOCK_STREAM)
has_v6 = True
except socket.gaierror:
pass
with concurrent.futures.ThreadPoolExecutor(max_workers=len(CHECK_GATEWAYS) * 2 + 2) as pool:
gw_futures = {}
for name, proxy_v4, proxy_v6 in CHECK_GATEWAYS:
f4 = pool.submit(_check_one, name, proxy_v4, url, "-4")
gw_futures[f4] = True
if has_v6 and proxy_v6:
f6 = pool.submit(_check_one, name, proxy_v6, url, "-6")
gw_futures[f6] = True
assets_future = pool.submit(_find_assets, CHECK_GATEWAYS[0][1], url)
for future in concurrent.futures.as_completed(gw_futures):
name, ipver, status, code = future.result()
self.send_sse("gateway", {
"name": name, "ipver": ipver,
"status": status, "http_code": code,
})
assets = assets_future.result()
# Phase 2: throttle detection
if assets:
asset_url = assets[0]
with concurrent.futures.ThreadPoolExecutor(max_workers=len(CHECK_GATEWAYS)) as pool:
throttle_futures = [
pool.submit(_check_throttle, name, proxy, asset_url)
for name, proxy, _pv6 in CHECK_GATEWAYS
]
for future in concurrent.futures.as_completed(throttle_futures):
gw_name, result = future.result()
if result and result.get("throttled"):
self.send_sse("throttle", {
"gateway": gw_name, "result": result,
})
self.send_sse("done", {})
else:
# JSON response (curl/scripts)
result = check_site(domain, file_url=file_url)
if file_url:
speed = {}
def on_speed(gw_name, res):
speed[gw_name] = res
run_speed_test(file_url, on_speed)
result["speed"] = speed
self.send_json(result)
def main():
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment