fix: move HEAD request out of critical path — non-blocking background thread

The 10s HEAD request for Content-Length blocked the download endpoint
response. nginx proxy in front (bench.srv2.sechpoint.app:443 → :5000)
timed out waiting, returned empty response → 'data is null' JS error.

Now wget starts immediately, response returns instantly, and HEAD
runs in a daemon thread to populate content_length for ETA later.
This commit is contained in:
Claus Lohmar 2026-07-22 12:16:45 +00:00
parent c31ad3409a
commit adfb68fe6b

View file

@ -20,6 +20,7 @@ from logging.handlers import RotatingFileHandler
import os
import shutil
import subprocess
import threading
import time
import uuid
from pathlib import Path
@ -262,17 +263,7 @@ async def upload_raw(request: Request):
if free_gb < 50:
logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
# Try to get file size via HEAD request (for speed estimation)
content_length = 0
try:
head_resp = http_requests.head(url, timeout=10, allow_redirects=True)
cl = head_resp.headers.get("Content-Length")
if cl:
content_length = int(cl)
logger.info("Download size from HEAD: %.1f GiB", content_length / (1024**3))
except Exception:
logger.info("Could not determine download size (HEAD failed — will skip ETA check)")
# Start wget immediately (HEAD request for size happens in background)
logger.info("Starting background download: %s%s", url, dest)
try:
proc = subprocess.Popen(
@ -288,10 +279,22 @@ async def upload_raw(request: Request):
"vmid": vmid,
"vm_name": vm_name,
"start_time": time.time(),
"content_length": content_length,
"content_length": 0,
"_last_logged_bytes": 0,
}
# Fire-and-forget HEAD request to get file size for ETA (non-blocking)
def _fetch_content_length():
try:
head_resp = http_requests.head(url, timeout=5, allow_redirects=True)
cl = head_resp.headers.get("Content-Length")
if cl and filename in _active_downloads:
_active_downloads[filename]["content_length"] = int(cl)
logger.info("Download size from HEAD: %.1f GiB", int(cl) / (1024**3))
except Exception:
pass
threading.Thread(target=_fetch_content_length, daemon=True).start()
return JSONResponse({
"phase": "downloading",
"filename": filename,