From a19ddc14f68c6dfc7572294807038b5ba555226a Mon Sep 17 00:00:00 2001 From: Will Miao Date: Sat, 13 Jun 2026 20:06:58 +0800 Subject: [PATCH] perf(preview): restore Linux sendfile, add cache headers, increase chunk size (#975) - Restrict manual video streaming to Windows only (sys.platform == 'win32'); Linux/macOS now uses kernel sendfile (zero-copy DMA) via aiohttp FileResponse - Add Cache-Control: public, max-age=86400 to streaming responses so browsers cache video previews across scroll cycles - Increase chunk size from 256KB to 1MB to reduce async iteration overhead on Windows where streaming is still required --- py/routes/handlers/preview_handlers.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/py/routes/handlers/preview_handlers.py b/py/routes/handlers/preview_handlers.py index 4b88cb4e..e7de3aaa 100644 --- a/py/routes/handlers/preview_handlers.py +++ b/py/routes/handlers/preview_handlers.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging import mimetypes +import sys import urllib.parse from pathlib import Path @@ -13,7 +14,7 @@ from ...config import config as global_config logger = logging.getLogger(__name__) -_CHUNK_SIZE = 256 * 1024 # 256 KB +_CHUNK_SIZE = 1024 * 1024 # 1 MB — balance between streaming iteration overhead and per-chunk memory # Video file extensions that bypass native sendfile on Windows # to avoid IOCP/ProactorEventLoop crashes during client disconnect. @@ -55,12 +56,13 @@ class PreviewHandler: logger.debug("Preview file not found at %s", str(resolved)) raise web.HTTPNotFound(text="Preview file not found") - # Video files: stream manually to avoid Windows native sendfile crash. - # aiohttp's FileResponse uses _sendfile_native on Windows (IOCP-based), - # which breaks when the client disconnects mid-transfer — this happens - # constantly when users scroll through a gallery of animated previews. + # Video files on Windows: stream manually to avoid Windows IOCP native + # sendfile crash when the client disconnects mid-transfer (happens + # constantly when users scroll through a gallery of animated previews). + # On Linux/macOS, web.FileResponse uses kernel sendfile (zero-copy DMA) + # and does not have this issue, so it is safe and much faster. suffix = resolved.suffix.lower() - if suffix in _VIDEO_EXTENSIONS: + if suffix in _VIDEO_EXTENSIONS and sys.platform == "win32": return await self._stream_file(request, resolved) # aiohttp's FileResponse handles range requests and content headers for us. @@ -83,6 +85,10 @@ class PreviewHandler: resp.content_type = content_type resp.content_length = file_size + # Allow browser caching: video previews rarely change during a session. + # The frontend already appends ?t={version} to bust cache on update. + resp.headers["Cache-Control"] = "public, max-age=86400" + await resp.prepare(request) try: