Les méthodes marquées comme oneway
ne sont pas bloquées. Pour les méthodes qui ne sont pas marquées comme oneway
, l'appel de méthode d'un client est bloqué jusqu'à ce que le serveur ait terminé l'exécution ou appelé un rappel synchrone (selon la limite atteinte en premier).
Les implémentations de méthodes de serveur ne peuvent appeler qu'un seul rappel synchrone. Les appels de rappel supplémentaires sont supprimés et consignés en tant qu'erreurs. Si une méthode est censée renvoyer des valeurs via un rappel et qu'elle ne l'appelle pas, cela est consigné en tant qu'erreur et signalé au client en tant qu'erreur de transport.
Threads en mode passthrough
En mode passthrough, la plupart des appels sont synchrones. Toutefois, pour conserver le comportement prévu, à savoir que les appels oneway
ne bloquent pas le client, un thread est créé pour chaque processus. Pour en savoir plus, consultez la présentation de HIDL.
Threads dans les HAL liées
Pour traiter les appels RPC entrants (y compris les rappels asynchrones des HAL aux utilisateurs des HAL) et les 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 des 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'un thread soit disponible.
Si le serveur ne comporte qu'un seul thread, les appels au serveur sont effectués dans l'ordre. Un serveur avec plusieurs threads peut terminer les appels dans le désordre, même si le client n'a qu'un seul thread. Toutefois, pour un objet d'interface donné, les appels oneway
sont garantis d'être triés (voir Modèle de threads de serveur). Pour un serveur multithread qui héberge plusieurs interfaces, les appels oneway
à différentes interfaces peuvent être traités simultanément les uns avec les autres ou avec d'autres appels bloquants.
Plusieurs appels imbriqués sont envoyés sur le même thread hwbinder. Par exemple, si un processus (A) effectue un appel synchrone à partir d'un thread hwbinder dans le processus (B), puis que le processus (B) effectue un appel synchrone dans le processus (A), l'appel est exécuté sur le thread hwbinder d'origine dans (A), qui est bloqué sur l'appel d'origine. Cette optimisation permet d'avoir un serveur à thread unique capable de gérer les appels imbriqués, mais elle ne s'applique pas aux cas où les appels passent par une autre séquence d'appels IPC. Par exemple, si le processus (B) a effectué un appel de liaison/vndbinder qui a appelé un processus (C), puis que le processus (C) appelle (A), il ne peut pas être traité sur le thread d'origine dans (A).
Modèle de threads de serveur
À l'exception du mode passthrough, les implémentations de serveur des interfaces HIDL résident dans un processus différent de celui du client et nécessitent un ou plusieurs threads en attente d'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 comporte plusieurs threads dans le pool de threads, il peut recevoir des appels entrants simultanés sur l'une de ses interfaces (en C++, cela signifie que les données partagées doivent être verrouillées avec soin).
Les appels à sens unique 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
sont toujours sérialisés, mais method3
peut s'exécuter en parallèle avec method1
et method2
.
Un seul thread d'exécution client peut entraîner une exécution simultanée sur un serveur avec plusieurs threads de deux manières:
- Les appels
oneway
ne sont pas bloqués. Si un appeloneway
est exécuté, puis qu'un appel nononeway
est appelé, le serveur peut exécuter l'appeloneway
et l'appel nononeway
simultanément. - Les méthodes de serveur qui transfèrent des données avec des rappels synchrones peuvent débloquer le client dès que le rappel est appelé depuis le serveur.
Dans la deuxième méthode, tout code de la fonction de serveur qui s'exécute après l'appel du rappel peut s'exécuter simultanément, le serveur traitant les appels ultérieurs du client. Cela inclut le code dans la fonction du serveur et les destructeurs automatiques qui s'exécutent à la fin de la fonction. Si le serveur comporte plusieurs threads dans son pool de threads, des problèmes de simultanéité surviennent même si les appels ne proviennent que d'un seul thread client. (Si un HAL servi par un processus nécessite plusieurs threads, tous les HAL ont 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 le débloquer. Le client procède en parallèle avec tout ce que l'implémentation du serveur fait après avoir appelé le rappel (ce qui peut inclure l'exécution de destructeurs). Le code de la fonction du 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 il peut être exécuté simultanément avec les futurs appels du client (sauf si le pool de threads du serveur ne comporte qu'un seul thread).
En plus des rappels synchrones, les appels oneway
d'un client à thread unique peuvent être géré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 différentes interfaces. Les appels oneway
sur la même interface sont toujours sérialisés.
Remarque:Nous vous recommandons vivement de renvoyer les fonctions de serveur 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 is called, // and the client resumes execution. ... return Void(); // is basically a no-op };
Modèle de threads client
Le modèle de threads 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 les appels bloqués, le client est bloqué jusqu'à ce qu'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é avecReturn::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 réussite, la fonction de rappel que le client transmet en tant qu'argument est toujours appelée par le serveur avant que la fonction elle-même ne renvoie une valeur. Le rappel est exécuté sur le même thread que l'appel de fonction. Les implémentateurs doivent donc faire attention à maintenir les verrous pendant les appels de fonction (et les éviter complètement lorsque cela est possible). Une fonction sans instruction generates
ni mot clé oneway
est toujours bloquante. Le client se bloque jusqu'à ce que le serveur renvoie un objet Return<void>
.
Appels à sens unique
Lorsqu'une fonction est marquée oneway
, le client renvoie immédiatement et n'attend pas que le serveur termine l'appel de fonction. En surface (et de manière globale), cela signifie que l'appel de fonction prend la moitié du temps, car il exécute la moitié du code. Toutefois, lorsque vous écrivez des implémentations sensibles aux performances, cela a des implications en termes de planification. Normalement, l'utilisation d'un appel à sens unique entraîne la poursuite 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 vers le processus appelé. Il s'agit d'une optimisation des performances dans le liaisonneur. Pour les services pour lesquels l'appel à sens unique doit être exécuté dans le processus cible avec une priorité élevée, la stratégie de planification du service destinataire peut être modifiée. En C++, l'utilisation de la méthode setMinSchedulerPolicy
de libhidltransport
avec les priorités et les règles d'ordonnancement définies dans sched.h
garantit que tous les appels au service s'exécutent au moins avec la stratégie et la priorité d'ordonnancement définies.