Modèles de filetage

Les méthodes marquées comme étant oneway ne bloquent pas. Pour les méthodes non marquées comme oneway , l'appel de méthode d'un client sera bloqué jusqu'à ce que le serveur ait terminé son exécution ou appelé un rappel synchrone (selon la première éventualité). Les implémentations de méthodes serveur peuvent appeler au plus un rappel synchrone ; les appels de rappel supplémentaires sont rejetés et enregistrés comme erreurs. Si une méthode est censée renvoyer des valeurs via un rappel et n'appelle pas son rappel, cela est enregistré comme une erreur et signalé comme une erreur de transport au client.

Threads en mode passthrough

En mode passthrough, la plupart des appels sont synchrones. Cependant, pour préserver le comportement prévu selon lequel les appels oneway ne bloquent pas le client, un thread est créé pour chaque processus. Pour plus de détails, consultez la présentation HIDL .

Fils dans les HAL liants

Pour répondre aux appels RPC entrants (y compris les rappels asynchrones des HAL aux utilisateurs de HAL) et aux notifications de décès, un pool de threads est associé à chaque processus qui utilise HIDL. Si un seul processus implémente plusieurs interfaces HIDL et/ou gestionnaires de notification de décès, son pool de threads est partagé entre tous. Lorsqu'un processus reçoit un appel de méthode entrant d'un client, il sélectionne un thread libre dans le pool de threads et exécute l'appel sur ce thread. Si aucun thread libre n'est disponible, il se bloque jusqu'à ce qu'il y en ait un.

Si le serveur n'a qu'un seul thread, les appels au serveur sont effectués dans l'ordre. Un serveur avec plusieurs threads peut effectuer des appels dans le désordre même si le client n'a qu'un seul thread. Cependant, pour un objet d'interface donné, les appels oneway sont garantis d'être ordonnés (voir Modèle de thread du serveur ). Pour un serveur multithread qui héberge plusieurs interfaces, les appels oneway vers différentes interfaces peuvent être traités simultanément les uns avec les autres ou avec d'autres appels bloquants.

Plusieurs appels imbriqués seront envoyés sur le même thread hwbinder. Par exemple, si un processus (A) effectue un appel synchrone depuis un thread hwbinder vers le processus (B), puis que le processus (B) effectue un appel synchrone vers le processus (A), l'appel sera exécuté sur le thread hwbinder d'origine. en (A) qui est bloqué sur l'appel d'origine. Cette optimisation permet d'avoir un serveur monothread capable de gérer les appels imbriqués, mais elle ne s'étend pas aux cas où les appels transitent par une autre séquence d'appels IPC. Par exemple, si le processus (B) a effectué un appel binder/vndbinder qui a appelé un processus (C), puis que le processus (C) rappelle dans (A), il ne peut pas être servi sur le thread d'origine dans (A).

Modèle de thread du serveur

À l'exception du mode passthrough, les implémentations serveur des interfaces HIDL vivent dans un processus différent de celui du client et nécessitent un ou plusieurs threads en attente des appels de méthode entrants. Ces threads constituent le pool de threads du serveur ; le serveur peut décider du nombre de threads qu'il souhaite exécuter dans son pool de threads et peut utiliser une taille de pool de threads de un pour sérialiser tous les appels sur ses interfaces. Si le serveur a plus d'un thread dans le pool de threads, il peut recevoir des appels entrants simultanés sur n'importe laquelle de ses interfaces (en C++, cela signifie que les données partagées doivent être soigneusement verrouillées).

Les appels unidirectionnels vers la même interface sont sérialisés. Si un client multithread appelle method1 et method2 sur l'interface IFoo et method3 sur l'interface IBar , method1 et method2 seront toujours sérialisées, mais method3 peut s'exécuter en parallèle avec method1 et method2 .

Un seul thread d'exécution client peut provoquer une exécution simultanée sur un serveur avec plusieurs threads de deux manières :

  • les appels oneway ne bloquent pas. Si un appel oneway est exécuté puis qu'un appel non oneway est appelé, le serveur peut exécuter simultanément l'appel oneway et l'appel non oneway .
  • Les méthodes serveur qui renvoient les données avec des rappels synchrones peuvent débloquer le client dès que le rappel est appelé depuis le serveur.

Pour la deuxième manière, tout code de la fonction serveur qui s'exécute après l'appel du rappel peut s'exécuter simultanément, le serveur gérant les appels ultérieurs du client. Cela inclut le code dans la fonction serveur et les destructeurs automatiques qui s'exécutent à la fin de la fonction. Si le serveur possède plusieurs threads dans son pool de threads, des problèmes de concurrence surviennent même si les appels proviennent d'un seul thread client. (Si un HAL servi par un processus nécessite plusieurs threads, tous les HAL auront plusieurs threads car le pool de threads est partagé par processus.)

Dès que le serveur appelle le rappel fourni, le transport peut appeler le rappel implémenté sur le client et débloquer le client. Le client procède en parallèle avec tout ce que fait l'implémentation du serveur après avoir appelé le rappel (qui peut inclure l'exécution de destructeurs). Le code dans la fonction serveur après le rappel ne bloque plus le client (tant que le pool de threads du serveur dispose de suffisamment de threads pour gérer les appels entrants), mais peut être exécuté en même temps que les futurs appels du client (sauf si le pool de threads du serveur n'a qu'un seul thread). ).

En plus des rappels synchrones, les appels oneway provenant d'un client monothread peuvent être traités simultanément par un serveur avec plusieurs threads dans son pool de threads, mais uniquement si ces appels oneway sont exécutés sur des interfaces différentes. les appels oneway sur la même interface sont toujours sérialisés.

Remarque : Nous encourageons fortement les fonctions serveur à revenir dès qu'elles ont appelé la fonction de rappel.

Par exemple (en C++) :

Return<void> someMethod(someMethod_cb _cb) {
    // Do some processing, then call callback with return data
    hidl_vec<uint32_t> vec = ...
    _cb(vec);
    // At this point, the client's callback will be called,
    // and the client will resume execution.
    ...
    return Void(); // is basically a no-op
};

Modèle de thread client

Le modèle de thread sur le client diffère entre les appels non bloquants (fonctions marquées avec le mot clé oneway ) et les appels bloquants (fonctions pour lesquelles le mot clé oneway n'est pas spécifié).

Bloquer les appels

Pour bloquer les appels, le client bloque jusqu'à ce que l'un des événements suivants se produise :

  • Une erreur de transport se produit ; l'objet Return contient un état d'erreur qui peut être récupéré avec Return::isOk() .
  • L'implémentation du serveur appelle le rappel (s'il y en avait un).
  • L'implémentation du serveur renvoie une valeur (s'il n'y avait pas de paramètre de rappel).

En cas de succès, la fonction de rappel que le client passe en argument est toujours appelée par le serveur avant le retour de la fonction elle-même. Le rappel est exécuté sur le même thread sur lequel l'appel de fonction est effectué, les implémenteurs doivent donc faire attention au maintien des verrous pendant les appels de fonction (et les éviter complètement lorsque cela est possible). Une fonction sans instruction generates ou sans mot-clé oneway est toujours bloquante ; le client bloque jusqu'à ce que le serveur renvoie un objet Return<void> .

Appels aller simple

Lorsqu'une fonction est marquée oneway , le client revient immédiatement et n'attend pas que le serveur termine son appel de fonction. En apparence (et globalement), cela signifie que l'appel de fonction prend la moitié du temps car il exécute la moitié du code, mais lors de l'écriture d'implémentations sensibles aux performances, cela a certaines implications en termes de planification. Normalement, l'utilisation d'un appel unidirectionnel entraîne le maintien de la planification de l'appelant, tandis que l'utilisation d'un appel synchrone normal entraîne le transfert immédiat du planificateur de l'appelant au processus appelé. Il s’agit d’une optimisation des performances en classeur. Pour les services où l'appel unidirectionnel doit être exécuté dans le processus cible avec une priorité élevée, la politique de planification du service récepteur peut être modifiée. En C++, l'utilisation de la méthode setMinSchedulerPolicy de libhidltransport avec les priorités et politiques du planificateur définies dans sched.h garantit que tous les appels dans le service s'exécutent au moins selon la politique et la priorité de planification définies.