fix(preview): revert to FileResponse as default for all platforms (#975)

The previous commit (a19ddc14) restored Linux sendfile but kept the
manual streaming path for Windows via sys.platform guard. A Windows
user reports performance is still worse than v1.0.5.

Switch back to web.FileResponse for all files on all platforms as the
default. The IOCP crash is an edge case (fast scrolling through many
video previews) that affects few users, while the Python chunked I/O
performance penalty affects everyone.

_stream_file() is kept as an unused fallback for a future compat
setting toggle.
This commit is contained in:
Will Miao
2026-06-13 21:43:44 +08:00
parent a19ddc14f6
commit 138024aefe

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
import logging import logging
import mimetypes import mimetypes
import sys
import urllib.parse import urllib.parse
from pathlib import Path from pathlib import Path
@@ -56,16 +55,11 @@ class PreviewHandler:
logger.debug("Preview file not found at %s", str(resolved)) logger.debug("Preview file not found at %s", str(resolved))
raise web.HTTPNotFound(text="Preview file not found") raise web.HTTPNotFound(text="Preview file not found")
# Video files on Windows: stream manually to avoid Windows IOCP native # aiohttp's FileResponse handles range requests, content headers, and
# sendfile crash when the client disconnects mid-transfer (happens # uses kernel sendfile (zero-copy DMA) on Linux/macOS. On Windows it
# constantly when users scroll through a gallery of animated previews). # uses IOCP-based _sendfile_native which can crash when the client
# On Linux/macOS, web.FileResponse uses kernel sendfile (zero-copy DMA) # disconnects mid-transfer during fast scrolling. The _stream_file()
# and does not have this issue, so it is safe and much faster. # fallback is kept for a future compat toggle.
suffix = resolved.suffix.lower()
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.
return web.FileResponse(path=resolved, chunk_size=_CHUNK_SIZE) return web.FileResponse(path=resolved, chunk_size=_CHUNK_SIZE)
async def _stream_file( async def _stream_file(