Il existe dans php-src une pull request, la numéro 15603, qui t'aurait permis de nommer ton propre share curl persistant avec une chaîne d'identifiant arbitraire, puis de le modifier ensuite via curl_share_setopt(). Elle a été rejetée. Le remplacement, la PR 16937, dérive l'identifiant des options elles-mêmes, rend l'objet retourné immuable, et lève une ValueError à la seconde où tu lui demandes de partager les cookies. C'est cette version qui a remporté le vote et qui est devenue curl_share_init_persistent() dans PHP 8.5. Laurent Mn a mesuré le résultat sur un build 8.5.6 compilé à la main et en a sorti un très joli chiffre. Je veux défendre l'idée que le chiffre est la petite histoire, et que la pull request rejetée est la grande.
$share = curl_share_init_persistent([
CURL_LOCK_DATA_SSL_SESSION,
CURL_LOCK_DATA_CONNECT,
]);
$ch = curl_init('https://api.example.com/orders');
curl_setopt($ch, CURLOPT_SHARE, $share);
curl_exec($ch);Voici le mécanisme, parce que tu ne peux pas juger la conception sans lui. L'extension curl garde une table de hachage appelée persistent_curlsh dans ses globals de module. Cette table est construite dans PHP_GINIT_FUNCTION et détruite dans PHP_GSHUTDOWN_FUNCTION, ce qui veut dire qu'elle naît au démarrage du worker et meurt avec lui, et qu'un cycle de requête normal ne la remet jamais à zéro. Ton tableau d'options est replié en un masque de quatre bits : le DNS au bit 0, la session SSL au bit 1, le cache de connexions au bit 2, PSL au bit 3. PHP cherche ce masque, te rend un objet enveloppe bon marché autour du CURLSH qu'il trouve, et n'en construit un nouveau que si rien ne correspond. Appeler la fonction à chaque requête n'est pas une fuite, c'est l'usage documenté.
Regarde maintenant ce que ce schéma d'identité t'achète. Tu ne peux pas entrer en collision avec une autre partie de ton code, parce que deux appelants qui demandent les mêmes drapeaux obtiennent par définition le même share sous-jacent, et que deux appelants qui demandent des drapeaux différents ne peuvent pas atterrir par accident dans le même pool. Tu ne peux pas faire fuiter un cookie de session d'un visiteur vers la réponse d'un autre, parce que CURL_LOCK_DATA_COOKIE ne figure tout simplement pas sur la liste acceptée aux côtés de DNS, SSL_SESSION, CONNECT et PSL. Tu ne peux pas reconfigurer un share que trois autres requêtes référencent déjà, parce que le handle n'accepte aucun setopt du tout. Chacune de ces garanties existe parce que quelqu'un, sur la liste internals, a d'abord plaidé pour la version mutable et a perdu.
Le contre-argument honnête, c'est que ça coûte quelque chose à certains. Il y a des boîtes qui ont un vrai besoin de partager un pot à cookies entre les appels sortants d'un worker, et pour elles la réponse est un non sec, sans issue de secours, pas même un flag ini à activer soi-même. L'immuabilité veut aussi dire que tu ne peux pas régler un share persistant différemment d'un autre une fois qu'il existe. Si tu fais partie de ces équipes qui lisent les sources C avant de livrer, et l'auteur ici a fait exactement ça dans ext/curl/share.c plutôt que de faire confiance à une page de manuel toute fraîche, l'API mutable aurait très bien tenu entre tes mains. Sauf que les API de bibliothèque standard ne sont pas écrites pour cette équipe. Elles sont écrites pour la base de code où quelqu'un copie un extrait de blog dans un contrôleur à 18h40 un vendredi, et dans cette base de code un pot à cookies partagé, c'est un incident de sécurité avec une fin en forme de CVE.
Ce qui m'amène à expliquer pourquoi je me méfie quand on ouvre avec le benchmark. Le résultat mesuré est un régime stable à environ un neuvième de la référence, une baisse de 88 à 89 pour cent, avec l'étape de handshake TLS qui tombe littéralement à zéro après chauffe et un écart type de 0,024 ms. Sauf que tout ça tourne en loopback contre un serveur TLS Python sur 127.0.0.1:8443, et l'auteur le dit franchement au lieu de le maquiller. Il a aussi dû activer disable_nagle_algorithm pour tuer un artefact de delayed ACK qui avait gonflé un passage précédent de 40 ms, ce qui te montre à quel point un microbenchmark mesure facilement la mauvaise chose. Le pourcentage est une propriété de ce montage. Ce qui se généralise, c'est le mécanisme : la résolution DNS, la connexion TCP et la négociation TLS cessent d'avoir lieu à chaque requête après la première, et sur un vrai réseau ces trois étapes valent bien plus de millisecondes absolues que ce que le loopback peut montrer.
Les réserves opérationnelles méritent autant d'affiche que le gain. La portée est par worker, donc cinquante enfants FPM signifient cinquante démarrages à froid après chaque déploiement, payés chacun par la requête malchanceuse qui tombe sur un processus neuf. La première requête en mode persistant coûtait environ 1,9 ms dans le test, indiscernable de la référence, exactement ce à quoi tu t'attends. Ensuite pm.max_requests recycle le processus et tu repaies l'addition, ce qui transforme du jour au lendemain un réglage d'hygiène mémoire en réglage de performance. Et CURLOPT_MAXCONNECTS change de sens : le cache de connexions sert désormais toute la vie d'un worker et non plus une seule requête, donc une limite dimensionnée pour une requête va évincer des sockets que tu croyais chaudes. Si ton worker parle à deux ou trois services internes toute la journée, c'est presque de l'argent gratuit. S'il tape à chaque fois un hôte différent et imprévisible, il n'y a rien à réutiliser et rien à gagner.
Deux détails plus modestes de la même version, bons à connaître avant que quelqu'un n'ouvre un ticket confus. Comparer deux handles avec === renvoie false même pour des drapeaux identiques, parce que chaque appel frappe un nouvel objet enveloppe autour de la même ressource partagée : utilise == ou rappelle simplement la fonction et laisse la recherche faire son travail. Et curl_close() ainsi que curl_share_close() sont toutes deux dépréciées en 8.5, maintenant que CurlHandle et CurlShareHandle se libèrent via le ramasse-miettes. Si tu es sur Symfony, le câblage passe par un décorateur autour de http_client qui pousse CURLOPT_SHARE dans le sac extra.curl, supporté depuis 6.3, et ça n'a d'effet que sous CurlHttpClient. NativeHttpClient t'ignorera poliment.
Voilà donc ce que j'aimerais vraiment entendre de ta part. Les internals ont sacrifié un cas d'usage légitime pour rendre un mésusage impossible, et je pense qu'ils ont eu raison, mais je tiens cette position depuis le confort de n'avoir jamais eu besoin d'un pot à cookies partagé entre requêtes sortantes. Et toi ? Plus concrètement : sais-tu combien d'hôtes distincts en aval l'un de tes workers contacte au cours de sa vie, ou est-ce un chiffre que personne dans ton équipe n'a jamais sorti des logs ?




Commentaires
Pas encore de commentaire — écris le premier.
Lance la discussion
Pas de compte ni de mot de passe — saisis simplement ton adresse e-mail et nous t’envoyons un lien de connexion à usage unique. Première visite ? Tout se met en place automatiquement.
Ton évaluation sera appliquée automatiquement après ta connexion.
Vérifie ta boîte mail
Nous avons envoyé un lien de connexion à …. Ouvre-le sur cet appareil — cet onglet te connectera automatiquement.
Rien reçu ? Vérifiez le dossier spam — et marquez le message « Non spam » pour qu'il arrive directement la prochaine fois.