Description
PlaylistNotifier._init fans out one GET /Playlists/{id}/Items per playlist, all at the same time and with no concurrency limit. On a library with many playlists this floods the connection and takes the rest of the app down with it.
https://github.com/DonutWare/Fladder/blob/develop/lib/providers/playlist_provider.dart#L73-L86
playlists?.forEach(
(playlist) async {
final itemList = await api.playlistsPlaylistIdItemsGet(
playlistId: playlist.id,
...
forEach with an async callback does not await anything, so every request starts at once.
On an account with 369 playlists that is 369 concurrent requests. What I measured:
- 352 of the 369 came back
500, served by the reverse proxy in front of Jellyfin rather than by Jellyfin itself — the proxy simply gives up under the burst.
- Unrelated calls issued in that same moment took 10–11 s instead of their usual 300–500 ms. Requests sent a second later, once the burst had drained, were back to normal.
- The app shows "Offline" while it lasts.
ConnectivityStatus._probeReachability gives /System/Info/Public a 2 s timeout, and that probe cannot get through the congestion, so the client decides the server is unreachable even though it is answering.
The failures are also unhandled. Response.bodyOrThrow throws inside the async closure passed to forEach, so nothing catches it and it surfaces as an unhandled async exception:
Unhandled Exception: Could not fetch the response for GET .../Playlists/<id>/Items. Status code: 500
#0 Response.bodyOrThrow (package:chopper/src/response.dart:92:7)
#1 JellyService.playlistsPlaylistIdItemsGet (package:fladder/providers/service_provider.dart:1363:54)
#2 PlaylistNotifier._init.<anonymous closure> (package:fladder/providers/playlist_provider.dart:75:26)
So there are three separate things here:
- no limit on how many of these requests are in flight,
- no error handling if one of them fails,
- the state being computed is only "does this playlist already contain the current item", which is what the add-to-playlist sheet needs. Computing it eagerly for every playlist on init looks avoidable — it could be resolved lazily for the playlists actually shown.
Anything that puts a proxy, a tunnel or a rate limiter between the client and Jellyfin will hit this earlier than a direct LAN connection, but the burst itself is client side and platform independent.
Reproduction steps
1. Use a Jellyfin account that has a few hundred playlists
2. Open Fladder and let it load
3. Watch the server or proxy logs: one GET /Playlists/<id>/Items per playlist, all fired at once
4. Requests issued during the burst stall for ~10 s, many playlist calls fail,
and the app flips to "Offline" while the server is in fact answering
Screenshots
Not applicable, the evidence is in the logs below.
Logs
# Unrelated calls caught in the burst, hostname and ids redacted
INFO: --> GET https://jellyfin.example.com/UserViews?userId=<redacted>
INFO: <-- 200 OK GET https://jellyfin.example.com/UserViews?userId=<redacted> (11667ms, 8906-byte body)
INFO: --> GET https://jellyfin.example.com/UserViews?userId=<redacted>
INFO: <-- 200 OK GET https://jellyfin.example.com/UserViews?userId=<redacted> (11709ms, 8906-byte body)
# Same session, once the burst had drained
INFO: <-- 200 OK GET https://jellyfin.example.com/Users/<redacted>/Items/Latest?... (340ms, 2-byte body)
INFO: <-- 200 OK GET https://jellyfin.example.com/Users/<redacted>/Items/Latest?... (370ms, 222524-byte body)
# Response code tally for the whole session
45 <-- 200
352 <-- 500 # all of them GET /Playlists/<id>/Items
# 369 distinct playlist ids, one request each, all concurrent
Platform
Windows
App Version
0.11.0, local build of develop
Jellyfin server
Docker, reached through an openresty reverse proxy and a Cloudflare Tunnel
Description
PlaylistNotifier._initfans out oneGET /Playlists/{id}/Itemsper playlist, all at the same time and with no concurrency limit. On a library with many playlists this floods the connection and takes the rest of the app down with it.https://github.com/DonutWare/Fladder/blob/develop/lib/providers/playlist_provider.dart#L73-L86
forEachwith anasynccallback does not await anything, so every request starts at once.On an account with 369 playlists that is 369 concurrent requests. What I measured:
500, served by the reverse proxy in front of Jellyfin rather than by Jellyfin itself — the proxy simply gives up under the burst.ConnectivityStatus._probeReachabilitygives/System/Info/Publica 2 s timeout, and that probe cannot get through the congestion, so the client decides the server is unreachable even though it is answering.The failures are also unhandled.
Response.bodyOrThrowthrows inside theasyncclosure passed toforEach, so nothing catches it and it surfaces as an unhandled async exception:So there are three separate things here:
Anything that puts a proxy, a tunnel or a rate limiter between the client and Jellyfin will hit this earlier than a direct LAN connection, but the burst itself is client side and platform independent.
Reproduction steps
Screenshots
Not applicable, the evidence is in the logs below.
Logs
Platform
Windows
App Version
0.11.0, local build of
developJellyfin server
Docker, reached through an openresty reverse proxy and a Cloudflare Tunnel