mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-09 20:39:25 -03:00
fix(preview): stream video files manually to avoid Windows sendfile crash
aiohttp's FileResponse uses _sendfile_native on Windows (IOCP-based), which crashes with ov.getresult() when the client disconnects mid-transfer. This happens constantly when users scroll through a gallery of animated previews (video files like .mp4/.webm). Detect video extensions and stream manually via StreamResponse + chunked reads instead, gracefully handling ConnectionResetError. Images continue using FileResponse (small files, sendfile works fine). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import mimetypes
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -12,6 +13,12 @@ from ...config import config as global_config
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_CHUNK_SIZE = 256 * 1024 # 256 KB
|
||||||
|
|
||||||
|
# Video file extensions that bypass native sendfile on Windows
|
||||||
|
# to avoid IOCP/ProactorEventLoop crashes during client disconnect.
|
||||||
|
_VIDEO_EXTENSIONS = frozenset({".mp4", ".webm", ".mov", ".avi", ".mkv"})
|
||||||
|
|
||||||
|
|
||||||
class PreviewHandler:
|
class PreviewHandler:
|
||||||
"""Serve preview assets for the active library at request time."""
|
"""Serve preview assets for the active library at request time."""
|
||||||
@@ -48,8 +55,51 @@ 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: 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.
|
||||||
|
suffix = resolved.suffix.lower()
|
||||||
|
if suffix in _VIDEO_EXTENSIONS:
|
||||||
|
return await self._stream_file(request, resolved)
|
||||||
|
|
||||||
# aiohttp's FileResponse handles range requests and content headers for us.
|
# aiohttp's FileResponse handles range requests and content headers for us.
|
||||||
return web.FileResponse(path=resolved, chunk_size=256 * 1024)
|
return web.FileResponse(path=resolved, chunk_size=_CHUNK_SIZE)
|
||||||
|
|
||||||
|
async def _stream_file(
|
||||||
|
self, request: web.Request, path: Path
|
||||||
|
) -> web.StreamResponse:
|
||||||
|
"""Stream a file chunk-by-chunk, bypassing native sendfile.
|
||||||
|
|
||||||
|
This avoids the Windows IOCP ``_sendfile_native`` crash that occurs
|
||||||
|
when the client disconnects during a large file transfer.
|
||||||
|
"""
|
||||||
|
content_type, _ = mimetypes.guess_type(str(path))
|
||||||
|
if content_type is None:
|
||||||
|
content_type = "application/octet-stream"
|
||||||
|
|
||||||
|
file_size = path.stat().st_size
|
||||||
|
resp = web.StreamResponse()
|
||||||
|
resp.content_type = content_type
|
||||||
|
resp.content_length = file_size
|
||||||
|
|
||||||
|
await resp.prepare(request)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
while True:
|
||||||
|
chunk = f.read(_CHUNK_SIZE)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
await resp.write(chunk)
|
||||||
|
except (ConnectionResetError, ConnectionAbortedError):
|
||||||
|
# Client disconnected during streaming — expected when scrolling
|
||||||
|
# rapidly through a library with animated previews.
|
||||||
|
pass
|
||||||
|
except OSError as exc:
|
||||||
|
logger.debug("I/O error streaming preview %s: %s", path, exc)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["PreviewHandler"]
|
__all__ = ["PreviewHandler"]
|
||||||
|
|||||||
Reference in New Issue
Block a user