Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
16 mars 2026

Laravel 12.54 : 3 fixes qui évitent des incidents bêtes (Redis, queue, punycode)

Les notes de release qu’on ignore… jusqu’au jour où elles décrivent exactement ton incident. Laravel 12.54 est typiquement dans cette catégorie.

9 min de lecture
7 vues
réactions
Partager :
Laravel 12.54 : 3 fixes qui évitent des incidents bêtes (Redis, queue, punycode)

Il y a des releases Laravel qui ajoutent des trucs. Et il y en a d’autres qui retirent des emmerdes. La 12.54, c’est clairement la deuxième catégorie. Pas glamour, mais ultra « prod » : du rate limiting Redis qui te fait croire que ton code tourne alors qu’il est ignoré, une situation de deadlock côté queue quand une réservation de job part en exception, et un détail sur la validation d’URL qui finit en bug internationalisation le jour où un utilisateur copie-colle un domaine « bizarre ».

Je ne vais pas te refaire le changelog. Je vais te raconter comment ces bugs se manifestent dans la vraie vie, ce que tu peux observer, et ce que je rejouerais en test après upgrade.

1) Rate limiting Redis : quand ton callback after() est silencieusement ignoré

Le rate limiting Laravel, c’est souvent du « plomberie » qu’on touche une fois, puis qu’on oublie. Sauf que le jour où tu as un incident de bruteforce, de scraping, ou juste une vague de trafic, tu redécouvres très vite une vérité simple : si ton rate limiter ment, tes métriques et tes protections mentent aussi.

Le bug corrigé en 12.54 touche un cas qui fait mal parce qu’il est discret : avec Redis, sur certains chemins, le callback after() (celui où tu poses typiquement un log, un compteur, une métrique, un event) peut être ignoré. Et ça crée un piège classique : tu vois bien que tu « throttles », tu vois bien des 429 ou des refus, mais ce que tu pensais déclencher « après » ne part jamais. Résultat : tu crois que ton monitoring est en place. En réalité, il a des trous.

Ce genre de bug te flingue aussi les analyses post-mortem. Tu te dis « on aurait dû voir le pic dans X » ou « on aurait dû bannir Y après N tentatives ». Sauf que ton after() n’a pas été appelé, donc tes chiffres sont faux. Et le pire, c’est qu’en environnement de dev, tu peux ne jamais tomber dessus (store différent, timing différent, volume différent).

Concrètement, si tu as du code comme ça (ou équivalent), c’est exactement le genre d’endroit que je rejouerais après upgrade :

use Illuminate\Support\Facades\RateLimiter;

RateLimiter::attempt(
    'login:'.request()->ip(),
    $perMinute = 5,
    function () {
        // Tentative autorisée
        return true;
    },
    $decaySeconds = 60
);

RateLimiter::throttle('login:'.request()->ip())
    ->allow(5)
    ->every(60)
    ->then(
        function () {
            // Autorisé
        },
        function () {
            // Trop de tentatives
        }
    )
    ->after(function () {
        // Ici, beaucoup mettent un log / metric / audit trail.
        // Le bug : avec Redis, ce callback pouvait être ignoré dans certains cas.
        logger()->info('Rate limit touched');
    });

Mon avis terrain : si tu utilises Redis pour le rate limiting (ce qui est le cas « sérieux »), ce fix mérite vraiment l’upgrade, parce qu’il touche la fiabilité de tes signaux. Un rate limiter qui bloque mais ne trace pas correctement, ça donne une fausse impression de contrôle.

2) Queue : un deadlock qui ressemble à « les workers sont vivants mais rien n’avance »

Celui-là est vicieux. Le correctif vise un scénario où la queue peut se retrouver dans un état de blocage quand la réservation d’un job (le moment où un worker « prend » un job) déclenche une exception. Dit comme ça, ça semble rare. En prod, ce n’est pas si rare, parce que des exceptions au mauvais endroit, on en provoque tous sans le vouloir : problème réseau, table verrouillée, payload inattendu, Redis qui tousse, connexion DB qui saute, etc.

Le symptôme typique n’est pas un gros crash évident. C’est plutôt une impression de panne « molle » : les workers tournent, tes dashboards disent que ça consomme, mais tu as des jobs qui restent bloqués, des réservations qui ne se libèrent pas, ou des messages qui semblent disparaître puis réapparaître. Et là tu commences à faire n’importe quoi : redémarrer les workers, augmenter la concurrence, relancer Horizon, « purger pour voir ». Mauvaise idée, parce que si le problème est un verrou qui ne se relâche pas, tu viens juste d’augmenter la pression sur un verrou déjà malade.

Ce fix est important parce qu’il évite que l’exception pendant la réservation te laisse un état incohérent qui finit en deadlock. Ça ne rend pas ta queue « magique », mais ça enlève une classe d’incidents où la seule “solution” devient un redéploiement ou un restart brutal.

Ce que je conseillerais de vérifier après upgrade, sans tomber dans l’overkill : prends un job volontairement fragile (un job qui touche la DB et une API externe), force une exception au moment où il démarre, et observe si ton système se remet à avancer normalement sans intervention humaine. Si tu utilises Horizon, regarde surtout le comportement « global » : est-ce que les autres queues continuent ? Est-ce que des jobs restent indéfiniment en « reserved » ? Est-ce que ton taux de retry se met à exploser sans débit réel ?

Et si tu as déjà vécu un incident « workers up, throughput down », garde ce fix en tête. Ce type de panne fait perdre des heures, parce que c’est un bug de coordination, pas un bug applicatif simple.

3) Validation d’URL et punycode : le piège des domaines IDN en copy/paste

Les URLs, c’est un terrain où on se raconte des histoires. On valide « url », on se dit que c’est bon, et on passe à autre chose. Jusqu’au jour où tu as une app un peu internationale, ou juste des utilisateurs qui collent des URLs depuis une messagerie, un PDF, un navigateur mobile… et tu découvres les joies des noms de domaine internationalisés (IDN).

Le punycode, en gros, c’est la version ASCII d’un domaine avec des caractères non-ASCII. Un exemple connu : « münich.example » devient une forme encodée (du style xn--...). Le web vit avec ça depuis longtemps. Le problème, c’est que la validation, elle, peut être plus fragile : selon la manière dont tu normalises l’entrée, tu peux rejeter une URL parfaitement valide, ou au contraire accepter une forme qui n’est pas celle que tu pensais.

Laravel 12.54 corrige un comportement autour de la validation d’URL liée à ces cas punycode. Et ce n’est pas juste « pour faire joli ». C’est typiquement le genre de bug qui déclenche un ticket utilisateur incompréhensible (« je ne peux pas enregistrer mon site ») alors que toi, en local, tu testes avec https://example.com et tout va bien.

Mon conseil très simple : si tu stockes des URLs que tu vas réutiliser (callback, webhook, redirection, profil), décide si tu veux normaliser avant de valider, ou valider tel quel. Je préfère normaliser quand ça a du sens métier, parce que tu réduis les « mêmes valeurs en deux écritures ». Et si tu normalises, fais-le explicitement, pas “par chance”. Par exemple, convertir le host en ASCII punycode avec l’extension intl quand elle est dispo, puis valider et stocker la version normalisée.

$url = trim($inputUrl);

$parts = parse_url($url);
if ($parts !== false && isset($parts['host']) && function_exists('idn_to_ascii')) {
    $asciiHost = idn_to_ascii($parts['host'], IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
    if ($asciiHost) {
        $url = str_replace($parts['host'], $asciiHost, $url);
    }
}

validator(['url' => $url], ['url' => ['required', 'url']])->validate();

Ce n’est pas une recette universelle. Mais ça te donne un point d’accroche concret : si ton produit touche des publics non-US, si tu fais du B2B avec des sites locaux, ou si tu acceptes des URLs saisies par des humains, tu vas croiser des IDN. Autant ne pas se prendre une validation « capricieuse » pour une histoire d’encodage.

Le petit fix « database » que j’aime bien : migrate:fresh quand la base n’existe pas

Ce n’est pas le plus sexy, mais il évite des frictions bêtes en CI ou sur des environnements éphémères. Laravel 12.54 corrige un cas où migrate:fresh se comportait mal si la base n’existait pas encore. C’est typiquement le truc qui passe sous le radar jusqu’au jour où tu changes une stratégie de tests (database par branche, preview env, pipeline plus agressif), et tu te retrouves avec un échec « idiot » qui n’a rien à voir avec tes migrations.

Si ton pipeline crée la base à la volée (ou si tu relies ça à des conteneurs jetables), ce fix enlève un point de casse inutile. Et franchement, tout ce qui retire des faux négatifs en CI, je prends.

Ce que je rejouerais après l’upgrade vers 12.54 (sans transformer ça en projet)

Je ferais court et ciblé. D’abord, je rejoue un scénario de rate limiting « réel », avec Redis activé comme en prod, et je vérifie explicitement que mon instrumentation liée au throttling se déclenche bien. Pas juste « ça renvoie 429 ». Je veux voir mes logs/metrics/événements se remplir comme prévu, sinon je ne sais pas ce que je crois savoir.

Ensuite, je mets la queue sous un minimum de stress. Pas besoin de 50k jobs. Juste assez pour voir le système encaisser un job qui lève une exception au mauvais moment, puis repartir sans état zombie. Si tu as Horizon, c’est le moment de regarder les graphes de throughput et de pending, pas juste « le worker est green ».

Et enfin, je teste deux ou trois URLs que je ne tape jamais en dev : un domaine avec accent, une URL copiée depuis un navigateur mobile, et un host en punycode direct. Si ton produit s’en fiche, ok. Si ton produit stocke des URLs et les réutilise, tu veux savoir si la validation est cohérente et si tu normalises correctement.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !