Skip to content

Libraries page silently drops any library too large to enumerate within the 8s HTTP timeout. #13

Description

@Schmidsfeld

Summary

On a server with large TV and Music libraries, the Libraries page shows only Movies. TV (55,738 episodes) and Music (193,414 tracks) are missing entirely — no error, no warning, no log line. The summary tiles are wrong to match ("Libraries: 1", "Total Items: 3,349", "Storage Used: 2.57 TB").

The cause is two things combining:

  1. LibraryOverviewService::mediaItems() enumerates every item in a library — full Fields=MediaSources,RunTimeTicks,DateCreated objects, paged 500 at a time — in order to build one library card.
  2. JellyfinClient::getJson() has a hardcoded 8-second timeout with no config override, and LibraryOverviewService::data() wraps each library in catch (\Throwable) { continue; }.

So any library whose 500-item page cannot be served by Jellyfin in under 8 seconds is dropped without a trace.

Environment

Jellydash 1.6.0 (ghcr.io/themartz90/jellydash@sha256:8bd83201bf9e0532bc7be9c0881a2f20468853c5a899cad7c8c58888b7618ded, built 2026-08-23)
Deployment Docker, SQLite (DB_DRIVER=sqlite3)
Jellyfin 10.11.11, x64, Linux
PHP 8.3.6, memory_limit=256M, max_execution_time=0 (CLI warmer)
LIBRARIES_CACHE_TTL 300 (default)
Auth Admin API token

Jellyfin and Jellydash run as containers on the same host, on the same Docker bridge network.

Reproduction

Run the app's own code path inside the container:

$c = new \Mk\Framework\Jellyfin\JellyfinClient();
$c->items([
  'ParentId' => '<tv library id>',
  'Recursive' => 'true',
  'IncludeItemTypes' => 'Episode',
  'Fields' => 'MediaSources,RunTimeTicks,DateCreated',
]);

Result (consistent across repeated runs):

memory_limit=256M  max_execution_time=0
TV     THREW after 8.0s: RuntimeException: Jellyfin request failed:
       Operation timed out after 8000 milliseconds with 0 bytes received  peak=2MB
Music  THREW after 8.0s: RuntimeException: Jellyfin request failed:
       Operation timed out after 8001 milliseconds with 0 bytes received  peak=2MB

Note peak=2MB — this is not a memory problem, purely the timeout.

Timings

Measured from inside the Jellydash container with CURLOPT_TIMEOUT=300, issuing the exact query items() builds (one page, Limit=500):

Library TotalRecordCount One 500-item page Pages Full scan
Movies 3,349 10.0s 7 ~70s
TV 55,738 13.9s 112 ~26 min
Music 193,414 49.8s 387 ~5.4 hrs

All three exceed the 8s limit. Movies currently renders only because its cache was warmed at a moment when Jellyfin answered faster; at 10.0s it is now over the line too and disappears on some refresh cycles.

This is why raising the timeout is not on its own a fix: at ~50s/page, Music needs roughly 5.4 hours for one full scan, against a default LIBRARIES_CACHE_TTL of 300 seconds. It would never converge.

Two suggested fixes

1. Don't enumerate items to produce aggregates.

Everything the library card needs is available from TotalRecordCount without transferring any items — and the codebase already does exactly this elsewhere. breakdown() uses countItems()JellyfinClient::itemCount() (Limit=0), which is a single cheap call per type. The main card path in mediaItems() could use the same mechanism.

The one genuine exception is sizeBytes, which today is summed from each item's MediaSources. Jellyfin can't total that directly, so options are to keep the enumeration behind an explicit opt-in, fetch it with a much narrower Fields set, or drop the storage tile for libraries above some size. RunTimeTicks has the same shape — though for totalTime on a 193k-track library, an approximation or omission seems more useful than a 5-hour scan.

2. Don't fail silently.

catch (\Throwable) { continue; } at LibraryOverviewService.php:78 is what makes this so hard to diagnose from the outside — a library vanishes from the UI with no indication anything went wrong. Even keeping current behaviour, logging the library name and exception, and rendering a card in an error state ("couldn't load — timed out") would turn this from a mystery into something self-evident.

A configurable timeout (e.g. JELLYFIN_TIMEOUT) would also help operators on slower or larger servers, though per the timings above it isn't sufficient alone.

Relevant code

Scope

Worth saying: this affects the Libraries page only. items() has exactly one caller in the codebase (mediaItems()), so Now Playing, History, Statistics, Trending and the Jellyseerr page are all unaffected and work correctly here — the history poller records streams normally and 40 Jellyseerr requests synced without trouble. Thanks for the project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions