123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- \section{Motivation}
- On peut donc commencer à construire chaque worker. On imagine que chacun est
- capable de communiquer à l'extérieur vers la bonne partie du core à partir du
- moment où l'initialisation a fait partager une paire d'IPC pour les relier.
- On va donc devoir gérer les messages envoyés d'un processus à l'autre.
- Exactement, il va falloir raisonner en parlant des messages envoyés des
- processus vers le processus qu'on étudie en particulier. Pour s'intégrer plus
- facilement dans l'application, on ne va pas intégrer la gestion des événements
- directement dans le flot d'exécution de chaque partie du core. En effet, cela
- deviendrait difficile de raisonner en terme de zone de privilège étant donné que
- l'application elle-même (et non plus seulement la sandbox) devra savoir router
- les messages d'un module supérieur vers un module inférieur.
- On va donc créer un thread par processus qui sera chargé de recevoir et exécuter
- les messages puis exécuter les RPC: il s'agit donc d'une boucle événementielle
- assez classique, mais qui doit prendre en compte le modèle threadé de
- l'application sous-jacente. Une attention particulière doit être mise sur la
- gestion des deadlocks.
- Une IPC qui déclencherait une fonction d'un morceau de l'application qui
- déclencherait une nouvelle RPC dans un autre processus et attendrait le retour
- de fonction amènerait à une situation de deadlock, la boucle événementielle
- étant bloquée dans l'exécution de la première RPC.
- Pire encore, une RPC créant le verrouillage de'certains mutexes qui seraient
- également verrouillés par le résultat de la RPC aboutirait également au blocage
- complet de ce bout de l'application.
- Une fois ces problématiques en tête, il faut prendre en compte les mécanismes
- des systèmes d'exploitations pour la programmation asynchrone et écrire une
- abstraction qui permet de récupérer un message depuis l'une des IPC disponibles.
- C'est ainsi qu'apparaît \inltype{vlc_msg_poller_t} et l'API qui est liée.
- \section{Description de l'API de msg\_poller\_t}
- Pour continuer la suite, il faut parler des différents modèles de notification
- d'événements. Il existe deux grandes catégories de notification d'événement.
- Le modèle par complétion propose à l'utilisateur d'effectuer toutes les actions
- qu'il souhaite voir s'exécuter, puis lui demande d'attendre que l'une d'entre
- elles soit terminée. Le système d'exploitation signale alors la complétion en
- redonnant les paramètres permettant d'identifier quelle opération a réussi et
- sous quelles conditions. Les modèles par complétion impliquent notamment que
- chaque socket doit avoir son propre tampon de lecture côté utilisateur, parce
- qu'on ne sait pas quand les opérations seront effectivement exaucées.
- Le modèle par disponibilité demande à l'utilisateur d'enregistrer les
- descripteurs de fichier ainsi que les événements qu'il veut surveiller. Dès lors
- qu'un de ces événements surgit. Il indique à l'utilisateur de quel type
- d'événement il s'agissait ainsi que le descripeur de fichier associé.
- L'utilisateur peut alors effectuer l'opération qu'il souhaitait faire sur le
- descripteur de fichier. Étant donné que ce modèle ne fait pas effectivement les
- actions, il est possible de multiplexer un tampon de lecture pour tous les
- descripteurs de fichier.
- L'API de \inltype{msg_poller_t} est basée sur un modèle de complétion. Elle
- permet d'ajouter et supprimer des IPC puis d'attendre jusqu'à la réception
- complète d'un message.
- \begin{code}{c}{API de l'objet \inltype{msg_poller}}
- VLC_API void
- msg_poller_Init( vlc_msg_poller_t *p_poller );
- VLC_API void
- msg_poller_AddChannel( vlc_msg_poller_t *p_poller, vlc_process_ipc_t *p_ipc );
- VLC_API void
- msg_poller_RemoveChannel( vlc_msg_poller_t *p_poller, vlc_process_ipc_t *p_ipc );
- VLC_API int
- msg_poller_WaitMsg( vlc_msg_poller_t *p_poller, vlc_process_msg_t **p_msg,
- vlc_process_ipc_t **p_ipc );
- VLC_API void
- msg_poller_Release( vlc_msg_poller_t *p_poller );
- \end{code}
- L'implémentation utilise deux détails liés au système d'exploitation
- sous-jacent, qui seront exhaustivement expliqués dans leur partie respectives
- pour Linux et Windows. La distinction est importante pour pouvoir l'écrire
- autant sous la forme d'un modèle de complétion que d'un modèle par
- disponibilité. D'un côté, il faut attendre d'avoir reçu les données
- correspondant à un message, c'est-à-dire que l'on puisse être capable de lire
- les données brutes. De l'autre côté, il faut pouvoir construire le message en
- récupérant les données, les éventuels descripteurs de fichiers et en extrayant
- les métadonnées. La lecture se fait en appelant \inltype{vlc_ipc_RecvMsg}.
- Il faut voir que dans un modèle par complétion, l'appel à
- \inltype{vlc_ipc_RecvMsg} s'effectuera avant l'attente de notification de
- réalisation, tandis que dans un modèle par disponibilité l'appel à
- \inltype{vlc_ipc_RecvMsg} s'effectuera une fois la notification reçue.
- \section{Gestion du multiplexage des messages}
- % TODO: évolutivité, messages totalement asynchrones, cookies
- Une fois cette abstraction en place, chaque worker va pouvoir recevoir des
- messages venant de plusieurs sources. On peut commencer à tracer les premières
- communications entre modules, mais il n'est pas encore possible d'effectuer des
- RPC.\@ En effet, les messages que nous envoyons et le découpage que l'on a fait
- en eventloop ne permettent pas de recevoir une réponse. Or cette réponse est
- cruciale autant pour garder un modèle synchrone là où il est synchrone dans VLC
- de base que pour pouvoir récupérer le résultat de l'appel de la fonction dans
- l'autre processus. De plus, on peut vouloir faire évoluer l'architecture vers
- moins de synchronisation plus tard, et avoir un système s'approchant de
- l'asynchrone avec des promesses, des résultats futurs et de la synchronisation
- à la demande seulement.
- Une première idée serait de bloquer la boucle événementielle tant qu'aucun
- message de réponse n'est reçu, en écoutant directement sur l'IPC à qui on a
- envoyé le message. Conceptuellement, cela demande de séparer le canal de
- requête du canal de réponse pour éviter de confondre une requête du module
- jouant le rÔle de correspondant avec la réponse à la RPC du premier module.
- Cela rajoute donc déjà de la complexité dans l'application. Mais plus encore,
- cela ouvre la voie à des verrous très facile où un module ferait une RPC vers
- un autre module qui ferait une RPC vers le premier module, qui ne pourra pas
- répondre sans la boucle événementielle.
- Une autre idée est donc de copier une technique déjà utilisée dans d'autres
- projets, en particulier XCB, et dans des protocoles bien connus comme TCP.\@
- Nous allons introduire un compteur de RPC envoyés dans chaque processus, et
- prendre en compte valeur de ce compteur dans les messages. Lorsqu'un module
- voudra lancer une nouvelle RPC vers un autre module, il utilisera la valeur de
- ce compteur en début de message puis l'incrémentera. Lorsqu'un autre module
- voudra répondre à une requête, il réutilisera la valeur de ce compteur en début
- de réponse. Du point de vue du proxy, on peut récupérer un
- \inltype{vlc_msg_cookie_t} en utilisant \inltype{vlc_msg_SendMsgReply}, qui
- contiendra les détails de la requête, et il sera alors possible d'envoyer autant
- de requêtes que nécessaire puis attendre que les cookies soient utilisés par la
- boucle événementielle pour récupérer les réponses. Pour attendre la réponse
- sur un cookie en particulier, on utilisera alors \inltype{vlc_msg_wait_reply},
- puis on pourra récupérer la réponse dans l'instance du cookie.
- % https://xcb.freedesktop.org/manual/structxcb__void__cookie__t.html
- Le mécanisme est analogue à celui de \inltype{xcb_void_cookie_t}, allant jusqu'à
- en reprendre le nom, mais n'est pas basé sur de la génération de code et ne
- donne pas de cookie différent par type de requête. En réalité, les cookies
- devraient être pouvoir alloués sur la pile, par exemple, sans que cela soit
- gênant du point de vue de l'API.\@ L'unique liberté qui doit être interdite est
- de transférer le cookie à d'autres fonctions ou stocker le cookie en dehors de
- la fonction dans laquelle le RPC a été effectué. En effet, ce système donne
- l'impression de pouvoir faire de la programmation complètement asynchrone, mais
- le contexte dans lequel s'exécute le RPC n'est pas asynchrone. L'appel a lieu
- dans une architecture prévue pour être synchrone et la fonction devra retourner
- une valeur à la fin, il faut donc avoir terminé l'ensemble des requêtes
- associées à une fonction. La situation est similaire à l'implémentation de XCB
- dans Xlib: bien qu'XCB supporte l'écriture de requête asynchrone, Xlib ne peut
- pas fournir d'outils à travers son API pour gérer cette caractéristique. XCB a
- le droit d'être complètement asynchrone au sein d'un appel à une fonction de
- Xlib mais doit impérativement avoir terminé ses requêtes à la fin de l'appel de
- la fonction.
|