mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
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>
58 lines
1.9 KiB
Dart
58 lines
1.9 KiB
Dart
import 'package:test/test.dart';
|
||
import '../api/asset_failure_cache.dart';
|
||
|
||
void main() {
|
||
group('AssetFailureCache', () {
|
||
test('un hôte inconnu est considéré joignable', () {
|
||
final cache = AssetFailureCache();
|
||
expect(cache.isDown('cdn.example'), isFalse);
|
||
});
|
||
|
||
test('un échec isolé ne coupe pas l’hôte', () {
|
||
// Un 5xx passager ne doit pas priver l'utilisateur de ses logos
|
||
// pendant plusieurs minutes.
|
||
final cache = AssetFailureCache(threshold: 3);
|
||
cache.recordFailure('cdn.example');
|
||
expect(cache.isDown('cdn.example'), isFalse);
|
||
});
|
||
|
||
test('coupe l’hôte après le seuil d’échecs consécutifs', () {
|
||
final cache = AssetFailureCache(threshold: 3);
|
||
for (var i = 0; i < 3; i++) {
|
||
cache.recordFailure('cdn.example');
|
||
}
|
||
expect(cache.isDown('cdn.example'), isTrue);
|
||
});
|
||
|
||
test('un succès remet le compteur à zéro', () {
|
||
final cache = AssetFailureCache(threshold: 3);
|
||
cache.recordFailure('cdn.example');
|
||
cache.recordFailure('cdn.example');
|
||
cache.recordSuccess('cdn.example');
|
||
cache.recordFailure('cdn.example');
|
||
expect(cache.isDown('cdn.example'), isFalse);
|
||
});
|
||
|
||
test('la coupure expire après le TTL', () {
|
||
var now = DateTime(2026, 9, 19, 9);
|
||
final cache = AssetFailureCache(
|
||
threshold: 1,
|
||
ttl: const Duration(minutes: 5),
|
||
clock: () => now,
|
||
);
|
||
cache.recordFailure('cdn.example');
|
||
expect(cache.isDown('cdn.example'), isTrue);
|
||
|
||
now = now.add(const Duration(minutes: 6));
|
||
expect(cache.isDown('cdn.example'), isFalse);
|
||
});
|
||
|
||
test('les hôtes sont indépendants', () {
|
||
final cache = AssetFailureCache(threshold: 1);
|
||
cache.recordFailure('mort.example');
|
||
expect(cache.isDown('mort.example'), isTrue);
|
||
expect(cache.isDown('vivant.example'), isFalse);
|
||
});
|
||
});
|
||
}
|