sandbox_eventloop.tex 8.9 KB

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