\section{Gestion asynchrone}

Maintenant que j'ai choisi les solutions de communication entre les différents
processus, on peut s'intéresser à la manière de gérer les échanges de messages
venant potentiellement de plusieurs sources.

Linux dispose de plusieurs mécanismes pour effectuer des opérations asynchrones,
tous n'ayant pas les mêmes caractéristiques en terme de consommation mémoire ou
complexité.

\subsection{poll, select}

Parmi les solutions pour manipuler plusieurs descripteurs de fichier en même
temps, les solutions classiques sont \inltype{poll} et \inltype{select}. Les
deux fonctions marquent leur ancienneté par une mauvaise complexité
algorithmique dans leur exploitation, c'est-à-dire un temps linéaire en le
nombre de descripteurs lors de l'attente d'un événement.

Dans un premier temps, nous allons voir \inltype{poll}, à qui nous pouvons
indiquer l'ensemble des événements à surveiller et qui nous signalera lorsque
l'un des événements a été notifié. Le vériable descripteur ayant reçu la
notification n'est cependant pas précisé et il faut parcourir le tableau des
événements pour pouvoir le trouver.

\begin{code}{c}{Prototype de poll dans man 2 poll}
int poll(struct pollfd *fds, nfds_t nfds, int délai);
\end{code}

Cette méthode est aujourd'hui relativement remplacée par \inltype{epoll} et ne
nous intéressera pas.

Ensuite, \inltype{select} fournit le même mécanisme en indiquant par des
opérations bit à bit quels sont les descripteurs de fichier ayant reçu une
notification. En plus de souffrir du problème précédent obligeant à parcourir
l'ensemble des descripteurs, elle est donc également limitée en nombre de
descripteur de fichier possible.

\begin{code}{c}{Prototype de select et macros dans man 2 select}
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
\end{code}

\subsection{Asynchronous IO}

Les asynchronous IO, ou AIO, sont un mécanisme de notification de complétion des
requêtes de lecture et écriture. L'objectif final de l'implémentation des AIO
est d'arriver à faire du zéro-copie sur l'API réseau de Linux. En effet, dans un
modèle par complétion, on peut indiquer directement au noyau où stocker les
données reçues. Il faut néanmoins accepter la déception et voir que c'est
parfois aussi coûteux que de faire la copie, particulièrement sur les
transmissions de petite taille.

L'API est implémentée en userland dans glibc, est relativement coûteuse et ne
permet d'attendre la première opération qui aurait terminé comme le permettait
les primitives précédentes. Ell est également difficile à utiliser car les
notifications sont reçues sous forme de signal, de nouveau thread ou ne sont
pas envoyées. Elle est donc inutilisable pour notre application mais vaut le
coup d'être mentionnée pour la réalisation d'un \inltype{vlc_msg_poller_t}
zéro-copie dans le futur.

\begin{code}{c}{Extrait de l'API de AIO depuis man 2}
struct aiocb {
   /* The order of these fields is implementation-dependent */

   int             aio_fildes;     /* File descriptor */
   off_t           aio_offset;     /* File offset */
   volatile void  *aio_buf;        /* Location of buffer */
   size_t          aio_nbytes;     /* Length of transfer */
   int             aio_reqprio;    /* Request priority */
   struct sigevent aio_sigevent;   /* Notification method */
   int             aio_lio_opcode; /* Operation to be performed;
                                      lio_listio() only */

   /* Various implementation-internal fields not shown */
};

int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);
\end{code}

\subsection{epoll}

% TODO: https://lkml.org/lkml/2015/2/24/667
% TODO: https://lore.kernel.org/patchwork/patch/543311/
Linux fournit depuis assez récemment un mécanisme bien plus performant et avec
une interface bien plus proche des autres API standards. Ce mécanisme, epoll, a
longtemps été critiqué car bien qu'étant le dernier des mécanismes permettant ce
genre de programmation sur événement, il reproduisait des problèmes d'interface
qui avaient été résolus par les solutions précédentes sur les autres systèmes
depuis longtemps. C'est notamment des développeurs chez Nginx qui ont remonté
des problèmes dans leur utilisation qui a abouti à l'ajout de
\inltype{EPOLLEXCLUSIVE} dans le noyau Linux 4.5.

Dans notre cas, il nous fournit une interface fonctionnant sur la disponibilité
des descripteurs de fichiers, utilisable facilement pour construire notre
implémentation du modèle par complétion.

La complexité est en $O(1)$ et elle n'a pas de limitation sur le nombre de
descripteurs autre que la consommation mémoire. C'est aussi la méthode de
remplacement privilégiée pour \inltype{poll} et en concurrence directe avec les
AIO de la sous-section précédente.

\begin{code}{c}{API de epoll décrite dans man 2 epoll}
/* retourne un descripteur de fichier vers un objet epoll */
int epoll_create1(int flags);
/* attend l'arrivée d'un événement sur l'instance d'epoll donnée */
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);
/* permet d'ajouter, modifier ou enlever des associations d'événement à des
 * descripteurs de fichiers dans l'instance d'epoll */
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
\end{code}

\section{Implémentation de \texttt{vlc\_msg\_poller\_t}}

Je peux désormais présenter l'implémentation Linux de \inltype{vlc_msg_poller_t}
que j'ai réalisée dans le prototype, en utilisant epoll.

\inltype{vlc_msg_poller_t} est construite uniquement avec un descripteur de
fichier.

\begin{code}{c}{Structure vlc\_msg\_poller\_t dans le prototype Linux}
struct vlc_msg_poller_t {
    int fd;
}
\end{code}

La création se fait en appelant \inltype{epoll_create1} qui remplace la fonction
dépréciée \inltype{epoll_create}.

L'ajout d'une nouvelle IPC dans le poller utilise ensuite le pointeur
utilisateur de \inltype{epoll_event} pour garder une trace de l'IPC.

\begin{code}{c}{Implémentation de msg\_poller\_AddChannel dans le
    prototype Linux}
VLC_API void
msg_poller_AddChannel( vlc_msg_poller_t *p_poller, vlc_process_ipc_t *p_ipc )
{
    struct epoll_event event =
    {
        /* se déclenche lorsque des données peuvent être lues */
        .events = EPOLLIN,
        .data = {
            /* on peut aussi choisir de stocker seulement le file descriptor */
            //.fd  = p_ipc->fd,
            .ptr = p_ipc
        }
    };
    epoll_ctl( p_poller->fd, EPOLL_CTL_ADD, p_ipc->fd, &event);
}
\end{code}

De même, la suppression appelle seulement la fonction \inltype{epoll_ctl} avec
l'argument \inltype{EPOLL_CTL_DEL}.

Enfin, la fonction d'attente utilise \inltype{epoll_wait} pour attendre et
récupérer le pointeur vers l'IPC étant disponible pour l'opération de lecture.
Une fois l'IPC récupérée, le \inltype{vlc_msg_poller_t} peut lire les données et
extraire le message en suivant notre modèle par complétion.

\begin{code}{c}{Implémentation de msg\_poller\_WaitMsg dans le prototype Linux}
VLC_API int
msg_poller_WaitMsg( vlc_msg_poller_t *p_poller, vlc_process_msg_t **p_msg,
                    vlc_process_ipc_t **p_ipc )
{
    struct epoll_event event;
    int ret = epoll_wait( p_poller->fd, &event, 1, -1 );

    if( ret == -1 )
        return VLC_EGENERIC;

    *p_ipc = event.data.ptr;
    /* extraction du message depuis l'IPC */
    return vlc_ipc_RecvMsg( *p_ipc, p_msg );
}
\end{code}
