mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
Logos, guide TV et fluidité : couper le flot de requêtes mortes
Logos absents : l'hébergeur des picons du fournisseur répond 503 sur tout, y compris sa racine (serveur en maintenance). Le proxy relayait ce 503 au navigateur, qui redemandait chaque logo à chaque affichage de la grille — des centaines de requêtes sortantes mortes par seconde. - Un statut >= 400 sur une image rend désormais un pixel transparent avec `Cache-Control: max-age=600`, au lieu de propager l'erreur - Après trois échecs consécutifs, l'hôte est marqué hors service cinq minutes : plus aucun appel sortant, réponse servie localement - Le corps d'erreur amont est drainé, sinon la connexion restait ouverte jusqu'au timeout Guide TV très lent (4 à 8 s par chaîne dans les logs) : `player_api` demande un appel par chaîne, et le panneau met ~4 s à répondre — une grille de trente chaînes dépassait la minute. - Le dump `xmltv.php` du panneau passe en premier : une requête couvre toutes les chaînes, réutilisée trois heures - L'interrogation chaîne par chaîne reste le repli, puis la source XMLTV externe - Une source XMLTV en échec n'est plus retéléchargée à chaque consultation : quinze minutes de recul - Index borné à 48 h en avant : un dump national couvre sept jours pour un millier de chaînes, soit des centaines de Mo pour un guide qui n'affiche que le programme courant Lecture saccadée : le relais FFmpeg -> navigateur de `/turbo.ts` ne transmettait pas la pause du client à la source. Un lecteur plus lent que le flux laissait FFmpeg produire à pleine vitesse et le serveur empilait les paquets en mémoire, d'où la dérive derrière le direct. La contre-pression est maintenant propagée (`pipeWithBackpressure`). Le serveur tourne sur un seul isolate : le flot de requêtes mortes et les attentes EPG de plusieurs secondes partageaient la boucle d'événements avec les paquets vidéo. Tests ajoutés : cache d'échecs par hôte, contre-pression du relais, priorité des sources EPG, recul après échec XMLTV, horizon d'index. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2151f2820c
commit
b6ca9dab96
11 files changed
+716
-78
No files matched your search
@@ -19,6 +19,9 @@ class XmltvEpgService {
|
||||
required this.sourceUrls,
|
||||
this.refreshInterval = const Duration(hours: 6),
|
||||
this.retention = const Duration(hours: 6),
|
||||
this.horizon = const Duration(hours: 48),
|
||||
this.retryBackoff = const Duration(minutes: 15),
|
||||
this.downloadTimeout = const Duration(minutes: 5),
|
||||
http.Client? client,
|
||||
}) : _client = client ?? http.Client();
|
||||
|
||||
@@ -33,11 +36,32 @@ class XmltvEpgService {
|
||||
/// garder double la taille de l'index.
|
||||
final Duration retention;
|
||||
|
||||
/// Les programmes qui commencent au-delà de cet horizon sont écartés à
|
||||
/// l'indexation.
|
||||
///
|
||||
/// Un dump national couvre souvent sept jours pour un millier de chaînes ;
|
||||
/// tout garder en mémoire dans le conteneur coûte des centaines de
|
||||
/// mégaoctets pour un guide qui n'affiche que le programme courant et les
|
||||
/// suivants.
|
||||
final Duration horizon;
|
||||
|
||||
/// Délai minimal entre deux tentatives quand aucune source n'a répondu.
|
||||
///
|
||||
/// Sans ce recul, une source morte était retéléchargée à chaque consultation
|
||||
/// du guide — une requête sortante par chaîne affichée, soit exactement le
|
||||
/// flot qu'un index est censé éviter.
|
||||
final Duration retryBackoff;
|
||||
|
||||
/// Plafond de téléchargement d'un dump. Le guide est facultatif : mieux vaut
|
||||
/// abandonner et servir le panneau que faire attendre l'utilisateur.
|
||||
final Duration downloadTimeout;
|
||||
|
||||
final http.Client _client;
|
||||
|
||||
/// clé de chaîne normalisée → programmes triés par heure de début.
|
||||
Map<String, List<XmltvProgramme>> _index = {};
|
||||
DateTime? _indexedAt;
|
||||
DateTime? _failedAt;
|
||||
Future<void>? _refreshInFlight;
|
||||
|
||||
bool get hasData => _index.isNotEmpty;
|
||||
@@ -70,6 +94,14 @@ class XmltvEpgService {
|
||||
? null
|
||||
: DateTime.now().difference(_indexedAt!);
|
||||
if (age != null && age < refreshInterval) return Future.value();
|
||||
|
||||
final sinceFailure = _failedAt == null
|
||||
? null
|
||||
: DateTime.now().difference(_failedAt!);
|
||||
if (sinceFailure != null && sinceFailure < retryBackoff) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
return _refreshInFlight ??= _refresh().whenComplete(() {
|
||||
_refreshInFlight = null;
|
||||
});
|
||||
@@ -101,19 +133,20 @@ class XmltvEpgService {
|
||||
|
||||
if (ok == 0) {
|
||||
// Garder l'index précédent plutôt que de servir un guide vide.
|
||||
_failedAt = DateTime.now();
|
||||
print('[XmltvEpg] aucune source disponible, index précédent conservé');
|
||||
return;
|
||||
}
|
||||
|
||||
_index = merged;
|
||||
_indexedAt = DateTime.now();
|
||||
_failedAt = null;
|
||||
print('[XmltvEpg] index prêt : ${merged.length} chaînes');
|
||||
}
|
||||
|
||||
Future<String> _download(String url) async {
|
||||
final response = await _client
|
||||
.get(Uri.parse(url))
|
||||
.timeout(const Duration(minutes: 5));
|
||||
final response =
|
||||
await _client.get(Uri.parse(url)).timeout(downloadTimeout);
|
||||
if (response.statusCode != 200) {
|
||||
throw HttpException('HTTP ${response.statusCode}');
|
||||
}
|
||||
@@ -132,6 +165,7 @@ class XmltvEpgService {
|
||||
|
||||
Map<String, List<XmltvProgramme>> _parse(String xml) {
|
||||
final cutoff = DateTime.now().toUtc().subtract(retention);
|
||||
final limit = DateTime.now().toUtc().add(horizon);
|
||||
final byChannel = <String, List<XmltvProgramme>>{};
|
||||
|
||||
// Alias : plusieurs dumps déclarent <channel id="X"> avec un
|
||||
@@ -191,7 +225,8 @@ class XmltvEpgService {
|
||||
if (programmeChannel != null &&
|
||||
start != null &&
|
||||
stop != null &&
|
||||
stop.isAfter(cutoff)) {
|
||||
stop.isAfter(cutoff) &&
|
||||
start.isBefore(limit)) {
|
||||
final key = normalizeKey(programmeChannel);
|
||||
if (key.isNotEmpty) {
|
||||
byChannel.putIfAbsent(key, () => []).add(
|
||||
|
||||
Reference in new issue
Block a user