Aller au contenu principal

Au coeur d'Electron: Intégration de la boucle de Messages

· 4 mins de lecture

C'est le premier article d'une série qui explique le fonctionnement interne d'Electron. Cet article présente comment la boucle d’événements de Node est intégrée dans Electron avec Chromium.


Il y a eu de nombreuses tentatives d'utiliser Node pour la programmation GUI, comme node-gui pour les liaisons avec GTK+ et node-qt pour les liaisons avec QT. Mais aucune d'entre elle ne fonctionne en production parce que les kits d'outils d'interface graphique ont leurs propres boucles de message tandis que Node utilise libuv pour sa propre boucle d'événement, et le thread principal ne peut exécuter qu'une seule boucle en même temps. Ainsi, l’astuce courante pour exécuter la boucle de message GUI dans Node consiste à activer la boucle de message par l'intermédiaire d'un timer avec un très petit intervalle, ce qui ralentit la réponse de l’interface graphique et occupe beaucoup de ressources CPU.

Lors du développement d'Electron, nous avons rencontré le même problème, bien que de manière inversée : nous devions intégrer la boucle d'événements de Node dans la boucle message de Chromium.

Le processus principal et le processus de rendu

Avant de plonger dans les détails de l'intégration de la boucle de messages, je vais d'abord expliquer l'architecture multi-processus de Chromium.

Dans Electron, il y a deux types de processus : le processus principal et le processus de rendu (ceci est en fait extrêmement simplifié, pour une vue complète, voir Architecture multi-processus). Le processus principal est responsable de l'interface utilisateur comme la création de fenêtres, tandis que le processus de rendu ne s'occupe que de l'exécution et du rendu des pages Web.

Electron permet d'utiliser JavaScript pour contrôler à la fois le processus principal et le processus de rendu, ce qui signifie que nous devons intégrer Node dans les deux processus.

Remplacement de la boucle de message de Chromium par libuv

Ma première tentative fut de ré implémenter la boucle de message de Chromium avec libuv.

C'était facile pour le processus de rendu car sa boucle de message n'écoutait que les descripteurs de fichiers et les timers et je n'avais besoin que d'implémenter l'interface avec libuv.

Cependant, c’était beaucoup plus difficile pour le processus principal. Chaque plate-forme a son propre type de boucles de messages GUI. macOS Chromium utilise NSRunLoop, alors que Linux utilise glib. J'ai essayé de nombreux hacks pour extraire les descripteurs de fichiers sous-jacents des boucles de messages de l'interface native, et puis les fournir à libuv pour itération, mais j'ai toujours rencontré des cas particuliers qui ne fonctionnaient pas.

J'ai donc finalement ajouté un timer pour interroger la boucle du message de l'interface dans un petit intervalle. Comme un résultat le processus a une utilisation constante du processeur, et certaines opérations ont de longs retards.

Scrutation de la boucle d’événements de Node dans un thread distinct

Comme libuv a mûri, il a été possible d’adopter une autre approche.

Le concept de fd backend a été introduit dans libuv, qui est un descripteur de fichier (ou un manipulateur) que libuv interroge pour sa boucle d'événements. Donc, en interrogeant le backend fd, il est possible d’être averti lorsqu’il y a un nouvel événement dans libuv.

Donc, dans Electron, j'ai créé un thread séparé pour interroger le backend fd, et puisque j'utilise les appels du système pour scruter au lieu des API libuv, c'est thread safe sûr. Et chaque fois qu'il y a un nouvel événement dans la boucle d'événement de libuv, un message est posté dans la boucle de message de Chromium, et les événements de libuv sont alors traités dans le thread principal.

De cette façon, j'ai évité de patcher Chromium et Node, et le même code a été utilisé dans à la fois les processus principal et de rendu.

Le code

Vous pouvez trouver l'implémentation de l'intégration de la boucle message dans les fichiers node_bindings sous electron/atom/common/. Il peut être facilement réutilisé pour les projets souhaitant intégrer Node.

Update: l'Implementation a été déplacée dans electron/shell/common/node_bindings.cc.