a5e797e5db
Backport of the main-branch fix. Both guest-facing download routes append one storage read per photo and hand them to archiver, which drains them one at a time — so every read past the one being written parks an S3 socket holding unread bytes, and nothing reclaims them. archiver's abort() does not touch its source streams, and the SDK arms its socket timeout on a 3s delay then clears it as soon as response headers land, so a fast response never gets one. This is the mechanism behind the incident reported against the cached-zip builder: pooled sockets held with unread bytes, uploads and gallery reads starved behind them, a process restart the only way out. These two routes need no admin credentials to reach — any gallery guest can trigger them, and closing the tab mid-download was enough to strand every appended-but-undrained read. utils/archiveStreamGuard caps reads in flight at 2 and destroys whatever is still open on every exit, including the client disconnect. A cancelled download returns without reaching finalize(), which would otherwise reject with ABORTED and make the catch send JSON over a response whose ZIP headers had already gone out. A read that dies while still queued is reported so the archive is aborted rather than hanging when it reaches a dead stream. downloadZipService still has the same pattern on this branch and is deliberately untouched here — that is the cached-zip builder, whose fix is a separate PR on main and not yet backported. Local-filesystem installs are unaffected: they take archiver's file-path branch and open no sockets. Relates to issue 1399 Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local>