litespeedtech / litespeedtech/lscache_wp

Image Lazy Load + "Add Missing Sizes" + remote images: serial, uncached, blocking requests causing crazy TTFB

Open
#1,020 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
257
Forks
123
PR merge metrics
No merged PRs in 30d

Description

# Summary
When **Lazy Load Images** + **Add Missing Sizes** is enabled, `Media::_detect_dimensions()` calls `getimagesize()` directly on remote image URLs found in the page buffer. This downloads each and every remote image over HTTP, serially, with no caching and no timeout, during the output-buffer finalize stage. On any page containing multiple remote images without explicit width/height attributes (e.g. Google review avatars from lh3.googleusercontent.com), this adds ~650–800ms per image to TTFB, scaling linearly with image count.

In my case a Google Reviews widget with 30 avatars produced a CRAZY **~21.5s TTFB on every uncached load.**

# Affected code
src/media.cls.php:

_parse_img() → "Add missing dimensions" block:
```php
if ( $add_missing_sizes ) {
if ( empty( $attrs['width'] ) || 'auto' === $attrs['width'] || empty( $attrs['height'] ) ... ) {
$dimensions = $this->_detect_dimensions( $attrs['src'] );
```

_detect_dimensions():
```php
private function _detect_dimensions( $src ) {
$pathinfo = Utility::is_internal_file( $src );
if ( $pathinfo ) {
$src = $pathinfo[0];
} elseif ( apply_filters( 'litespeed_media_ignore_remote_missing_sizes', false ) ) {
return false;
}
if ( 0 === strpos( $src, '//' ) ) {
$src = 'https:' . $src;
}
try {
$sizes = getimagesize( $src ); // ← ⚠️ blocking remote HTTP download ⚠️
} catch ( \Exception $e ) {
return false;
}
...
}
```

# Root cause
1. getimagesize() on a remote URL downloads the image body to read header dimensions. No HEAD/ranged request, no stream size cap.
2. No timeout — relies on PHP default_socket_timeout (default 60s). A single slow/unresponsive remote host can stall the page for up to that long, per image.
3. No caching — dimensions are re-detected on every uncached page load, for every image, every time.
4. Serial — images are processed one after another in the foreach loop; total cost = sum of all fetches.
5. The `litespeed_media_ignore_remote_missing_sizes` defaults to `false`, i.e. THIS unsafe "fetch remote images" behavior is the default.

# Evidence
Added a temporary timing and logging around the getimagesize() call. Log excerpt (with Google account avatars):
```awk
getimagesize BEGIN: https://lh3.googleusercontent.com/a-/ALV-UjU...=s120-c-rp-mo-ba12-br100
getimagesize END : 784ms ...
getimagesize BEGIN: https://lh3.googleusercontent.com/a-/ALV-UjX...=s120-c-rp-mo-ba12-br100 (0.6ms after previous END)
getimagesize END : 806ms ...
getimagesize BEGIN: https://lh3.googleusercontent.com/a/ACg8ocK...=s120-c-rp-mo-ba12-br100
getimagesize END : 666ms ...
... (continues serially)
```
First BEGIN 02:59:11.493 → last END (20 images shown) 02:59:25.419 ≈ 14s for ~20 images
Average ~690ms/image (range 644–813ms)
Gap between each END and the next BEGIN ≈ 0.6ms → fully serial, no concurrency
Extrapolated to 30 avatars ≈ ~21s, matching the observed TTFB

# Suggested fixes
Reconsider the default of `litespeed_media_ignore_remote_missing_sizes`.
Defaulting to skip remote images (or at least documenting the risk prominently) would be much much safer.

# Workaround
```php
add_filter( 'litespeed_media_ignore_remote_missing_sizes', '__return_true' );
```

# Notes
When caching is enabled, this issue is easy to miss but still harmful! The slow render still happens on every cache miss, like first uncached visits. Cache warming/preload crawlers pays full cost.

Also, because application-level profilers (e.g. Query Monitor, Code Profiler) report a fast page-generation time (the work happens in the litespeed_buffer_finalize output stage, after PHP has "finished"), the delay is easily misattributed to the network, DNS, the theme, or unrelated plugins, costing significant debugging time before the true cause is found - like it happened to me.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in src/media.cls.php by tracing _parse_img() through its “Add missing dimensions” block to _detect_dimensions(), then inspect how the litespeed_media_ignore_remote_missing_sizes filter controls remote URLs. Reproduce the serial getimagesize() requests with several remote images; done means remote images no longer impose unsafe blocking work by default, with the behavior remaining explicit and documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.