Files
changedetection.io/changedetectionio/content_fetchers
dgtlmoonandClaude Opus 5 b61b83c35d
Build and push containers / metadata (push) Canceled after 0s
Build and push containers / build-push-containers (push) Canceled after 0s
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Canceled after 0s
ChangeDetection.io App Test / lint-code (push) Canceled after 0s
ChangeDetection.io App Test / lint-translations (push) Canceled after 0s
ChangeDetection.io App Test / lint-template-i18n (push) Canceled after 0s
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Canceled after 0s
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-11 (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-12 (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-13 (push) Canceled after 0s
ChangeDetection.io App Test / test-application-3-14 (push) Canceled after 0s
Fetch favicon candidates in parallel under one deadline, and stop the two ways it could hang (#4435)
Each candidate icon got its own fresh 2s AbortController, so the cost was n x 2s: a site
declaring five <link rel="icon"> variants spent 10s+ in here, sequentially, inside the
page - holding a browser, a worker and a proxy connection the whole time - to fetch
decoration.

Capping the total is not enough, and is a trap. Giving up early returns no icon, so
nothing is saved, favicon_is_expired() stays true, and the same cost is paid again on the
very next check - forever. Measured against a page with five hanging icons, one 404 and
one good one:

  sequential, 2s each : 10.0s, found the icon
  total cap only      :  3.0s, found NOTHING (and repeats every check)
  parallel + deadline :  3.0s, found the icon

So: fetch them all concurrently under one shared 3s AbortController and take the first
success in preference order - the array is already sorted largest-first then
apple-touch-icon, so this still returns the preferred icon rather than merely the
quickest to answer.

Two hangs fixed while in here:

- clearTimeout() fired before `await resp.blob()`, leaving the body read unguarded. The
  shared signal now covers it (aborting a signal errors the body stream too), so a slow
  or never-ending body is bounded like the headers.

- the FileReader promise had no reject path and resolved only from onloadend, reading
  reader.result unguarded. A FileReader failure threw inside the callback and left the
  promise permanently pending, with the timer already cleared - the favicon fetch then
  hung forever with nothing to stop it. It now always resolves.

Also skips an oversized icon from Content-Length before pulling its body down the wire,
where the server declares it.

Unchanged: the candidate collection and sort, the data: URI shortcut, the 1MB limit that
matches bump_favicon(). Verified no regression on the ordinary paths - a page with one
working icon still returns it in 0.0s, and a page with no <link> still falls back to
/favicon.ico.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 09:43:43 +02:00
..
2026-09-12 16:04:39 +02:00