Skip to content

fix(linux): persistent HTTP/1.1 connection pool to prevent parallel-request timeout#949

Open
bl4ckswordsman wants to merge 4 commits intoedde746:mainfrom
bl4ckswordsman:main
Open

fix(linux): persistent HTTP/1.1 connection pool to prevent parallel-request timeout#949
bl4ckswordsman wants to merge 4 commits intoedde746:mainfrom
bl4ckswordsman:main

Conversation

@bl4ckswordsman
Copy link
Copy Markdown

@bl4ckswordsman bl4ckswordsman commented Apr 29, 2026

Problem: Linux lacks a native HTTP/2 client in the Dart ecosystem (dart-lang/http#1385). Every PlexHttpClient created a fresh IOClient, meaning each of the 10+ parallel home-screen requests (on-deck, hubs, metadata) opened a separate TCP+TLS handshake. Dart's default maxConnectionsPerHost = 6 caused trailing requests to queue past the 10s connect timeout, triggering spurious failover to unreachable LAN/plex.direct candidates.

Additionally, PlexHttpClient.close() called IOClient.close() which called HttpClient.close() internally - destroying all keep-alive connections and forcing cold handshakes on every reconnect.

Fix:

  • Added a file-level HttpClient singleton for Linux with maxConnectionsPerHost = 12 and idleTimeout = 90s. createPlatformClient() wraps it so all PlexHttpClient instances share one pool.

  • Added closePlexClient() - no-op on Linux, normal close() elsewhere. PlexHttpClient.close() now delegates to this instead of calling _client.close() directly.

  • Added createProbeClient() - returns a fresh, disposable IOClient for connection testing that cannot accidentally close the singleton pool.

  • Stub versions of all three functions added to platform_http_client_stub.dart for web compile compatibility.

Android/iOS/Windows behaviour is unchanged.

Also fixes #929 in my testing.

@bl4ckswordsman
Copy link
Copy Markdown
Author

Hey @edde746 could I please get some input, is this not up to standards?

It's only 35 lines, linux-specific, and fixes some timeouts related to caddy/http2 use cases which I constantly get without this patch.

@edde746
Copy link
Copy Markdown
Owner

edde746 commented May 2, 2026

Been busy with adding Jellyfin support, I believe there was an issue with doing this which led to me switching to native HTTP clients where available. Also:

  1. createPlatformClient() now returns an IOClient backed by the Linux singleton, but several non-MediaServerHttpClient callers still call .close() directly. For example, Fribb refresh/download and Trakt disposal can close the shared HttpClient, leaving _linuxSingleton pointing at a closed client. Later requests would then fail unexpectedly.
  2. The transcoder warm-up cancellation guard currently does not work as intended because serverSupportsVideoTranscoding() is still launched once before the guarded block. It also means the probe can be launched twice when seedTranscoderVideoSupport == null.
  3. The PR mentions createProbeClient(), but the probe paths do not appear to use it yet. Either those paths should be wired to a disposable client, or the helper should be removed from this PR.

@bl4ckswordsman
Copy link
Copy Markdown
Author

bl4ckswordsman commented May 2, 2026

My bad for disturbing and interrupting your flow. And thanks for the input.

I removed the duplicate probe fire and unused createProbeClient().

Regarding the createPlatformClient references.
fribb_mapping_store.dart creates short-lived clients in two download methods and almost certainly closes them in a finally block, which would destroy the singleton on Linux. The tracker service constructors (TraktClient, MalClient, SimklClient, AnilistClient, OAuthProxyClient, DeviceCodeAuthServiceBase) store the result as a field and would do the same on disposal.

Since these callers have seemingly no reason to share the Plex connection pool, perhaps they should use a plain disposable client instead(?):

// fribb_mapping_store.dart and all tracker service constructors
- _http = httpClient ?? platform.createPlatformClient();
+ _http = httpClient ?? http.Client();

http.Client() on Linux would then resolve to a plain IOClient with its own HttpClient, so behaviour is unchanged, it just can't accidentally close the singleton.

What do you think about this? (whenever you have a few minutes to spare ofc)

I wanted to keep the patch clean and the scope of this PR as narrow as possible. But if it gets too broad or too complicated, and since you mentioned you had issues with this approach before, I might just have to give up on it and keep building a patched version on my own.

Thanks anyways!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

'PlexHttpException(connectionError: No route to host)' when Secure Connections set to Required in Plex settings

2 participants