I metodi contrassegnati come oneway
non vengono bloccati. Per i metodi non contrassegnati come
oneway
, la chiamata al metodo di un client si blocca finché il server non ha completato l'esecuzione o non ha chiamato un callback sincrono (a seconda dell'evento che si verifica per primo).
Le implementazioni dei metodi del server possono chiamare al massimo un callback sincrono. Le chiamate di callback aggiuntive vengono ignorate e registrate come errori. Se un metodo dovrebbe
restituire valori tramite callback e non chiama il relativo callback, viene registrato come
errore e segnalato come errore di trasporto al client.
Thread in modalità passthrough
In modalità passthrough, la maggior parte delle chiamate è sincrona. Tuttavia, per mantenere il comportamento previsto in cui le chiamate oneway
non bloccano il client, viene creato un thread per ogni processo. Per maggiori dettagli, consulta la panoramica di HIDL.
Thread nelle HAL con binder
Per gestire le chiamate RPC in arrivo (inclusi i callback asincroni dagli HAL agli utenti HAL) e le notifiche di decesso, a ogni processo che utilizza HIDL è associato un threadpool. Se un singolo processo implementa più interfacce HIDL e/o gestori di notifiche di morte, il pool di thread è condiviso tra tutti. Quando un processo riceve una chiamata di metodo in arrivo da un client, sceglie un thread libero dal threadpool ed esegue la chiamata su quel thread. Se non è disponibile alcun thread libero, il thread viene bloccato finché non ne viene trovato uno.
Se il server ha un solo thread, le chiamate al server vengono completate in ordine. Un server con più di un thread potrebbe completare le chiamate fuori sequenza anche se il client ha un solo thread. Tuttavia, per un determinato oggetto dell'interfaccia,
le chiamate a oneway
sono garantite come ordinate (vedi
Modello di threading del server). Per un server multithread che ospita più interfacce, le chiamate oneway
a interfacce diverse potrebbero essere elaborate contemporaneamente tra loro o con altre chiamate bloccanti.
Vengono inviate più chiamate nidificate nello stesso thread hwbinder. Ad esempio, se un processo (A) effettua una chiamata sincrona da un thread hwbinder al processo (B) e poi il processo (B) effettua una chiamata sincrona al processo (A), la chiamata viene eseguita sul thread hwbinder originale in (A), che è bloccato sulla chiamata originale. Questa ottimizzazione consente di avere un server a thread singolo in grado di gestire le chiamate nidificate, ma non si estende ai casi in cui le chiamate passano attraverso un'altra sequenza di chiamate IPC. Ad esempio, se il processo (B) ha effettuato una chiamata a binder/vndbinder che ha chiamato un processo (C) e poi il processo (C) richiama (A), non può essere eseguito nel thread originale in (A).
Modello di threading del server
Ad eccezione della modalità passthrough, le implementazioni server delle interfacce HIDL si trovano in un processo diverso rispetto al client e richiedono uno o più thread in attesa di chiamate di metodi in entrata. Questi thread sono il threadpool del server; il server può decidere quanti thread vuole eseguire nel threadpool e può utilizzare una dimensione del threadpool pari a 1 per serializzare tutte le chiamate sulle sue interfacce. Se il server ha più di un thread nel threadpool, può ricevere chiamate in arrivo concorrenti su qualsiasi interfaccia (in C++, ciò significa che i dati condivisi devono essere bloccati con attenzione).
Le chiamate unidirezionali nella stessa interfaccia vengono serializzate. Se un client multithread chiama method1
e method2
sull'interfacciaIFoo
e method3
sull'interfaccia IBar
,method1
e method2
vengono sempre serializzati, mamethod3
può essere eseguito in parallelo con method1
emethod2
.
Un singolo thread di esecuzione del client può causare l'esecuzione simultanea su un server con più thread in due modi:
- Le chiamate di
oneway
non vengono bloccate. Se viene eseguita una chiamataoneway
e poi viene chiamata una chiamata nononeway
, il server può eseguire contemporaneamente la chiamataoneway
e la chiamata nononeway
. - I metodi del server che trasmettono i dati con i callback sincroni possono sbloccare il client non appena il callback viene chiamato dal server.
Nel secondo caso, qualsiasi codice nella funzione del server che viene eseguito dopo la chiamata del callback può essere eseguito in modo concorrente, con il server che gestisce le chiamate successive del client. Sono inclusi il codice nella funzione del server e i distruttori automatici che vengono eseguiti alla fine della funzione. Se il server ha più di un thread nel pool di thread, si verificano problemi di concorrenza anche se le chiamate provengono da un solo thread client. Se un HAL servito da un processo richiede più thread, tutti gli HAL hanno più thread perché il threadpool è condiviso per processo.
Non appena il server chiama il callback fornito, il trasporto può chiamare il callback implementato sul client e sbloccarlo. Il client procede parallelamente a qualsiasi operazione eseguita dall'implementazione del server dopo aver chiamato il callback (che potrebbe includere l'esecuzione di distruttori). Il codice nella funzione del server dopo il callback non blocca più il client (a condizione che il pool di thread del server abbia thread sufficienti per gestire le chiamate in arrivo), ma potrebbe essere eseguito contemporaneamente alle chiamate future del client (a meno che il pool di thread del server non abbia un solo thread).
Oltre ai callback sincroni, le chiamate oneway
da un client a thread singolo possono essere gestite contemporaneamente da un server con più thread nel pool di thread, ma solo se queste chiamate oneway
vengono eseguite su interfacce diverse. Le chiamate a oneway
sulla stessa
interfaccia sono sempre serializzate.
Nota:consigliamo vivamente alle funzioni del server di rientrare non appena hanno chiamato la funzione di callback.
Ad esempio (in 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 };
Modello di threading del client
Il modello di threading sul client è diverso per le chiamate non bloccanti
(funzioni contrassegnate con la parola chiave oneway
) e per le chiamate bloccanti (funzioni per le quali non è specificata la parola chiave oneway
).
Blocca chiamate
Per il blocco delle chiamate, il client si blocca finché non si verifica una delle seguenti condizioni:
- Si verifica un errore di trasporto. L'oggetto
Return
contiene un stato di errore che può essere recuperato conReturn::isOk()
. - L'implementazione del server chiama il callback (se presente).
- L'implementazione del server restituisce un valore (se non è presente un parametro di callback).
In caso di esito positivo, la funzione di callback passata dal client come argomento viene sempre chiamata dal server prima che la funzione stessa ritorni. Il callback viene eseguito nello stesso thread in cui viene eseguita la chiamata alla funzione, pertanto gli implementatori devono fare attenzione a mantenere i blocchi durante le chiamate alle funzioni (ed evitarli del tutto, se possibile). Una funzione senza un'istruzione generates
o una parola chiave oneway
continua a bloccarsi; il client si blocca finché il server non restituisce un oggetto Return<void>
.
Chiamate unidirezionali
Quando una funzione è contrassegnata come oneway
, il client restituisce immediatamente il valore
e non attende che il server completi l'invocazione della chiamata di funzione. A prima vista (e in generale), la chiamata di funzione richiede metà del tempo perché viene eseguito metà del codice, ma quando si scrivono implementazioni sensibili alle prestazioni, questo ha alcune implicazioni di pianificazione. Normalmente,
l'utilizzo di una chiamata unidirezionale fa sì che il chiamante continui a essere pianificato, mentre
l'utilizzo di una normale chiamata sincrona fa sì che lo scheduler trasferisca immediatamente
dal chiamante al processo chiamato. Si tratta di un'ottimizzazione del rendimento in
binder. Per i servizi in cui la chiamata unidirezionale deve essere eseguita nel processo di destinazione con una priorità elevata, è possibile modificare il criterio di pianificazione del servizio di ricezione. In C++, l'utilizzo del metodo libhidltransport
setMinSchedulerPolicy
con le priorità e i criteri di pianificazione
definiti in sched.h
garantisce che tutte le chiamate al servizio vengano eseguite almeno con la priorità e il criterio di pianificazione impostati.