Une image de Mandelbrot se calcule pixel par pixel, chaque pixel étant indépendant des autres : elle se découpe donc en bandes que l’on peut confier à des processus différents. Comment occuper plusieurs processus sans lancer tous les calculs à la fois ? Et si l’un d’eux s’arrête avant de répondre ? Ce projet prolonge les messages et monitors d’AP6 par un coordinateur : le processus qui distribue les bandes, garde explicitement la file des travaux et recueille les résultats, sans rien calculer lui-même. Les processus qui calculent sont les travailleurs (workers).

Téléchargez le squelette ferme-rendu. Son README contient les contrats, les rappels Elixir, les étapes et les essais. Le calcul des pixels, le dessin SVG, la commande et la version séquentielle sont fournis ; vous construisez le module RenderFarm.Coordinator. Aucun frontend n’est à développer, et le projet n’a aucune dépendance à télécharger.

Le coordinateur lance deux bandes. La seconde répond d'abord ; une troisième peut alors démarrer.

Le schéma montre le cœur du sujet : avec une limite de deux calculs en vol, le coordinateur lance deux bandes ; la seconde répond la première, ce qui libère une place pour la troisième. L’ordre des réponses ne dit donc rien de la place des bandes dans l’image : c’est l’identifiant de chaque travail qui la donne.

Voir le résultat avant de répartir le travail

mix compile
mix test --only provided
mix render sequential 1 out/sequential.svg

Ouvrez l’image produite. Chaque bande a un identifiant et une position ; l’ordre des réponses ne doit pas décider de sa place dans l’image. Le test fourni, qui vérifie que les bandes couvrent chaque pixel une seule fois, passe dès le départ. Les cinq autres tests échouent avec « à compléter » tant que la coordination n’est pas écrite.

Le contrat à respecter est RenderFarm.run(jobs, work, limit) : jobs est une liste de couples {id, payload}, work une fonction pure appliquée à chaque payload, et le résultat rend, dans l’ordre initial, un {:ok, valeur} ou un {:error, raison} par identifiant. Les tests emploient aussi de petits calculs sans pixel : un échange de messages se comprend sur deux additions.

Construire le coordinateur

Vous écrirez la boucle qui lance au plus limit travailleurs, recueille leurs résultats par identifiant et remplit une place libérée par un autre travail. Les travailleurs calculent réellement les pixels ; le coordinateur reste disponible pour traiter leurs messages. Contrairement à AP7, les clients ne délèguent donc pas tous leur calcul à un unique GenServer.

Le README avance en deux temps. D’abord, recevoir sans confondre les travaux : avec spawn, send et receive comme en AP6, deux travailleurs répondent au coordinateur, qui range chaque résultat dans une map indexée par identifiant. Une question avant d’écrire la boucle : qui connaît le PID auquel répondre, et à quel moment faut-il le capturer ? Ensuite, limiter les calculs en cours : l’état explicite du coordinateur contient la file des travaux restants, la map des travailleurs actifs et la map des résultats obtenus.

mix test --only coordination
mix render parallel 4 out/parallel.svg

Le cas hors ordre est provoqué par des messages de libération, sans attendre une durée arbitraire. Gardez deux invariants : au plus limit travaux actifs, et un résultat final par identifiant. Une file vide n’est pas encore une preuve de terminaison : des calculs peuvent toujours être en cours. Il faut donc à la fois une file vide et aucun travailleur actif.

Réagir à une panne

Une panne, ici, est un travailleur qui s’arrête sans avoir envoyé de résultat : avec spawn seul, le coordinateur l’attendrait indéfiniment. spawn_monitor crée le processus en le surveillant, comme le monitor d’AP6 : à sa fin, le coordinateur reçoit un message :DOWN. Transformez cette fin sans réponse en résultat d’erreur, puis libérez sa place pour le travail suivant. Le socle ne réessaie pas automatiquement les calculs et ne détecte pas un travailleur qui reste vivant mais bloqué indéfiniment.

mix test --only monitoring
mix test

Comparez l’image séquentielle et l’image parallèle avant de comparer leurs temps. La parallélisation peut coûter plus cher pour une petite image ; le nombre de schedulers (les ordonnanceurs de la BEAM, en général un par cœur), le travail et la machine comptent. Une accélération n’est pas un critère de réussite.

La reprise limitée, les échéances, l’annulation ou une variante sous Task.Supervisor sont des prolongements guidés. Le README précise les messages supplémentaires et les limites à expliquer.

Ce projet noté débute pendant les trois heures d’AP9, puis se termine à la maison. Le démarrage est accompagné ; périmètre, barème et échéance seront précisés avant janvier, et les trois étapes ne sont pas un barème. Conservez le code, vos tests, une image, une trace commentée de trois travaux dont un échec, et COMPTE-RENDU.md, qui justifie la borne de concurrence et le critère de terminaison. Pour toute question, vous pouvez me contacter par email.

Pour les curieux : le fascicule

Un petit fascicule d’approfondissement accompagne ce sujet : les idées derrière le projet, leur histoire, ce qu’elles deviennent dans les systèmes réels et quoi lire ensuite. C’est un cadeau de fin de cours ; rien n’y est à rendre.

Ouvrir le fascicule (PDF)