H2o: Crash lors de la réinitialisation de la connexion lors du traitement des demandes de manière asynchrone

Créé le 7 nov. 2018  ·  8Commentaires  ·  Source: h2o/h2o

Tout d'abord, merci pour H2O, c'est super ! Nous utilisons la bibliothèque pour 'dnsdist-doh'. Ce rapport est d'environ 2.2.5.

Comme décrit dans #142, dnsdist-doh fonctionne en mode asynchrone. Une requête http2 est reçue puis transmise pour traitement DNS. Le retour est envoyé via un tube, qui est connecté à h2o_socket_read_start. Si des données sont reçues de là, elles contiennent un pointeur vers le « req » d'origine. Nous utilisons ensuite cette req pour envoyer la réponse DNS.

Ce code est dans : https://github.com/ahupowerdns/pdns/blob/dnsdist-doh/pdns/dnsdistdist/doh.cc

Le problème maintenant est que parfois, au moment où 'on_dnsdist' reçoit des données du canal, la connexion HTTP2 a déjà été réinitialisée. Comme l'a noté Valgrind :

==11799== Invalid write of size 4
==11799==    at 0x6490BF: finalostream_send (stream.c:341)
==11799==    by 0x639760: h2o_send_inline (request.c:513)
==11799==    by 0x61EF70: on_dnsdist(st_h2o_socket_t*, char const*) (doh.cc:538)
==11799==    by 0x62F267: read_on_ready (evloop.c.h:235)

Et c'est parce que ces données ont été libérées ici :

==11799==    at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11799==    by 0x643DFA: handle_rst_stream_frame (connection.c:797)
==11799==    by 0x641A9C: expect_default (connection.c:837)
==11799==    by 0x643AD0: parse_input (connection.c:873)
==11799==    by 0x643AD0: on_read (connection.c:901)
==11799==    by 0x62F267: read_on_ready (evloop.c.h:235)

Après avoir été alloué à l'origine ici :

==11799==  Block was alloc'd at
==11799==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11799==    by 0x64920D: h2o_mem_alloc (memory.h:328)
==11799==    by 0x64920D: h2o_http2_stream_open (stream.c:37)
==11799==    by 0x644AFC: handle_headers_frame (connection.c:596)
==11799==    by 0x641A9C: expect_default (connection.c:837)

Mon interprétation est que H2O a fermé ma connexion alors que je traitais encore la demande.

Ma question est : comment gérer cette situation ? Puis-je mettre un rappel quelque part? Ou puis-je convaincre H2O de garder ma demande en vie tant que je « possède » une demande ?

Toute aide est la bienvenue!

Commentaire le plus utile

Je pense que cela a réglé le problème ! C'est la disponibilité de doh.powerdns.org.
Existe-t-il une documentation de la bibliothèque h2o où je pourrais contribuer à une description des restrictions d'utilisation des fonctions _inline lorsque je travaille de manière asynchrone ?

image

Tous les 8 commentaires

Je soupçonne que mon problème est que j'utilise h2o_send_inline et que je devrais utiliser un générateur sur lequel stop() est appelé si la connexion disparaît, et que je devrais enregistrer le générateur avant de rendre le contrôle à la boucle d'événement h2o. N'hésitez pas à me dire si je pense dans le bon sens !

Je devrais utiliser un générateur sur lequel stop() est appelé si la connexion disparaît, et que je devrais enregistrer le générateur avant de rendre le contrôle à la boucle d'événement h2o.

Oui! Oui! Oui! Merci d'avoir cherché vous-même la bonne réponse !

Je pense que cela a réglé le problème ! C'est la disponibilité de doh.powerdns.org.
Existe-t-il une documentation de la bibliothèque h2o où je pourrais contribuer à une description des restrictions d'utilisation des fonctions _inline lorsque je travaille de manière asynchrone ?

image

Fantastique! Bon à savoir que dnsdist-doh fonctionne bien !

Malheureusement, nous n'avons pas de bon point pour la documentation, même si je pense que ce serait une bonne idée d'ajouter l'avertissement au commentaire de doc pour h2o_send_inline dans include/h2o.h.

Il peut également être utile de mentionner que h2o_send_inline() crée une copie de son entrée en tant que considération de performance. Cela signifie également que la fonction ne s'approprie pas le paramètre, ce dernier peut donc être désalloué immédiatement après son retour à l'appelant.

Je suis un peu confus par la documentation ajoutée en #1896 car cette dernière semble contredire ce qui a été dit en #142. J'utilise la boucle d'événements libh2o pour gérer les connexions à la base de données, mon workflow ressemble donc à ceci :

  1. Une requête HTTP est reçue par le serveur.
  2. Le rappel h2o_handler_t::on_req ne fait rien d'autre que démarrer une requête de base de données asynchrone (en utilisant h2o_socket_read_start() et h2o_socket_notify_write() si nécessaire) et retourner 0 - en particulier, il n'appelle pas h2o_start_response() ou h2o_send_inline() (ou quelque chose de similaire).
  3. Le contrôle revient à la boucle d'événement libh2o .
  4. Le résultat de la requête de base de données arrive et est géré par un rappel. Ce dernier appelle soit h2o_send_inline() soit h2o_start_response() suivi d'une série d'invocations h2o_send() .

D'après les commentaires du n° 142, c'est correct car au point 3, il n'y a pas assez d'informations pour générer les en-têtes de réponse HTTP (par exemple, la taille du corps de la réponse dépend du résultat de la requête de base de données, qui n'a pas encore été reçu), il est donc trop tôt pour enregistrer le générateur.

J'ai parcouru l' implémentation du proxy inverse , et il semble faire une chose plus ou moins similaire - pour autant que je sache, la première fois qu'il utilise h2o_start_response() est dans la fonction on_head() , qui, si j'ai bien compris, est appelé après que le serveur d'origine ait renvoyé la première partie de la réponse au proxy (ce qui correspond au point 4 de mon workflow). Par contre, d'après ma lecture de #1896 j'ai besoin de définir le rappel h2o_generator_t::stop et d'appeler h2o_start_response() au point 2 avant de retourner 0, afin d'éviter un accès potentiel à la mémoire désallouée . Alors, quelle est la bonne approche, ou est-ce que j'ai raté quelque chose ?

@volyrique Merci d'avoir soulevé la question. Je me rends compte maintenant que j'ai causé de la confusion.

Permettez-moi d'expliquer cela d'une manière différente.

h2o_req_t est supprimé lorsque le client ferme la connexion sous-jacente ou supprime brutalement un flux HTTP/2. Les applications asynchrones doivent détecter cela et éviter d'utiliser h2o_req_t par la suite.

Il y a deux façons d'y parvenir.

La première consiste à enregistrer le générateur sur h2o_req_t pour recevoir le rappel stop du générateur appelé. L'inconvénient, comme vous le soulignez correctement, est que le générateur est enregistré en appelant h2o_start_response : une fonction qui ne peut être appelée qu'une fois que le code d'état et les en-têtes de réponse sont prêts.

L'autre méthode consiste à allouer une mémoire avec un rappel dispose partir du pool de mémoire de la requête 1 . Le gestionnaire de proxy utilise cette technique (voir lib/core/proxy.c ligne 553 de la version 2.2.5 ) ; c'est on_generator_dispose est appelé lorsque le h2o_req_t est défaussé.

@ahupowerdns J'espère que ce commentaire clarifie mieux le fonctionnement de H2O. J'espère que vos modifications ne sont pas basées sur l'hypothèse erronée que le rappel stop sera invoqué avant même que vous n'appeliez h2o_start_response pour lier le générateur à la réponse. Et enfin, merci d'avoir soumis le #1896 ; Je vais apporter des modifications au PR pour refléter ce que j'ai écrit ici et le fusionner.

[1] La fonction utilisée pour allouer de la mémoire avec un rappel dispose est appelée h2o_mem_alloc_shared , car c'est l'une des capacités d'une allocation de mémoire "partagée" (c'est-à-dire comptée par référence).

dnsdist-doh utilise maintenant h2o_mem_alloc_shared qui est 1) plus facile et 2) en fait meilleur car il nous permet de stocker exactement quelle demande a été interrompue. En utilisant la méthode 'stop', vous obtenez un signal que 'req' doit s'arrêter, mais h2o s'avère réutiliser rapidement ce même req ptr dans la pratique, vous devez donc faire très attention à annuler le bon travail en suspens. En utilisant h2o_mem_alloc_shared, vous pouvez ajouter un pointeur vers votre propre état. Merci!

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

kazuho picture kazuho  ·  7Commentaires

concatime picture concatime  ·  3Commentaires

utrenkner picture utrenkner  ·  5Commentaires

proyb6 picture proyb6  ·  5Commentaires

Ys88 picture Ys88  ·  5Commentaires