Metody oznaczone jako oneway
nie są blokowane. W przypadku metod nieoznaczonych jako
oneway
wywołanie metody przez klienta jest blokowane, dopóki serwer nie zakończy wykonywania lub nie wywoła synchronicznego wywołania zwrotnego (zależnie od tego, co nastąpi wcześniej).
Implementacje metod serwera mogą wywołać maksymalnie 1 asyncjoniczne wywołanie zwrotne; dodatkowe wywołania są odrzucane i rejestrowane jako błędy. Jeśli metoda ma zwracać wartości za pomocą funkcji zwracającej wywołanie zwrotne, a nie wywoła tej funkcji, zostanie to odnotowane jako błąd i zgłoszone klientowi jako błąd transportu.
Wątek w trybie przekazywania
W trybie przekazywania większość połączeń jest synchroniczna. Jednak aby zachować zamierzone działanie, które polega na tym, że wywołania oneway
nie blokują klienta, dla każdego procesu tworzony jest wątek. Więcej informacji znajdziesz w artykule Omówienie HIDL.
Wątki w związanych interfejsach API
Aby obsługiwać przychodzące wywołania RPC (w tym asynchroniczne wywołania zwrotne z HAL do użytkowników HAL) oraz powiadomienia o zakończeniu działania, z każdym procesem, który korzysta z HIDL, skojarzony jest zespół wątków. Jeśli pojedynczy proces implementuje wiele interfejsów HIDL lub mechanizmów obsługi powiadomień o awarii, jego pulę wątków udostępnia się wszystkim tym mechanizmom. Gdy proces otrzyma przychodzące wywołanie metody od klienta, wybiera wolny wątek z grupy wątków i wykonuje wywołanie na tym wątku. Jeśli nie ma wolnego wątku, blokada trwa do momentu, gdy się pojawi.
Jeśli serwer ma tylko jeden wątek, wywołania do serwera są wykonywane w kolejności. Serwer z większą liczbą wątków może wykonywać wywołania w nieporządku, nawet jeśli klient ma tylko jeden wątek. Jednak w przypadku danego obiektu interfejsu wywołania oneway
są gwarantowane jako uporządkowane (patrz Model wątkowania serwera). W przypadku serwera wielowątkowego, który hostuje wiele interfejsów, wywołania oneway
do różnych interfejsów mogą być przetwarzane równolegle z innymi wywołaniami blokującymi lub innymi wywołaniami.
Wiele zagnieżdżonych wywołań jest wysyłanych na tym samym wątku hwbinder. Jeśli na przykład proces A wywołuje wywołanie synchroniczne z wątku hwbinder do procesu B, a potem proces B wywołuje wywołanie synchroniczne do procesu A, wywołanie jest wykonywane na pierwotnym wątku hwbinder w procesie A, który jest blokowany w pierwotnym wywołaniu. Ta optymalizacja umożliwia serwerowi jednowątkowemu obsługę zagnieżdżonych wywołań, ale nie dotyczy przypadków, gdy wywołania przechodzą przez inną sekwencję wywołań IPC. Jeśli na przykład proces (B) wykonał wywołanie binder/vndbinder, które wywołało proces (C), a proces (C) wywołał z powrotem proces (A), nie można go obsłużyć w pierwotnym wątku w procesie (A).
Model wątkowania serwera
Z wyjątkiem trybu przepuszczania implementacje interfejsów HIDL na serwerze działają w ramach innego procesu niż klient i potrzebują co najmniej 1 wątku oczekującego na przychodzące wywołania metod. Te wątki są puli wątków serwera. Serwer może określić, ile wątków ma działać w tej puli, i może użyć puli wątków o rozmiarze 1, aby serializować wszystkie wywołania w swoich interfejsach. Jeśli serwer ma więcej niż 1 wątek w zbiorze wątków, może odbierać przychodzące wywołania na dowolnym z interfejsów (w C++ oznacza to, że współdzielone dane muszą być starannie zablokowane).
Wywołania jednokierunkowe do tego samego interfejsu są serializowane. Jeśli wielowątkowy klient wywołuje funkcje method1
i method2
na interfejsie IFoo
oraz funkcję method3
na interfejsie IBar
, funkcje method1
i method2
są zawsze serializowane, ale funkcja method3
może działać równolegle z funkcjami method1
i method2
.
Pojedynczy wątek klienta może powodować równoczesne wykonywanie na serwerze z wieloma wątkami na 2 sposoby:
- Połączenia z numeru
oneway
nie są blokowane. Jeśli najpierw zostanie wykonane wywołanieoneway
, a następnie wywołanie niebędąceoneway
, serwer może wykonać wywołanieoneway
i wywołanie niebędąceoneway
jednocześnie. - Metody serwera, które zwracają dane za pomocą wywołań zwrotnych synchronicznych, mogą odblokować klienta, gdy tylko wywołanie zwrotne zostanie wywołane przez serwer.
W drugim przypadku każdy kod w funkcji serwera, który jest wykonywany po wywołaniu funkcji zwracającej podprogirma, może być wykonywany równolegle, a serwer obsługuje kolejne wywołania z klienta. Dotyczy to kodu w funkcji serwera i automatycznych destruktorów, które są wykonywane na końcu funkcji. Jeśli serwer ma więcej niż 1 wątek w zbiorze wątków, występują problemy z współbieżnością, nawet jeśli wywołania pochodzą tylko z 1 klienckiego wątku. (Jeśli jakikolwiek HAL obsługiwany przez proces potrzebuje wielu wątków, wszystkie HAL-e mają wiele wątków, ponieważ pula wątków jest współdzielona przez proces).
Gdy tylko serwer wywoła podany adres wywołania zwrotnego, transport może wywołać zaimplementowany adres wywołania zwrotnego na kliencie i odblokować klienta. Klient działa równolegle z implementacją serwera po wywołaniu przez niego funkcji zwracającej dane (co może obejmować uruchamianie destruktorów). Kod w funkcji serwera po wywołaniu zwrotnym nie blokuje już klienta (o ile pula wątków serwera ma wystarczającą liczbę wątków do obsługi przychodzących wywołań), ale może być wykonywany równolegle z przychodzącymi wywołaniami z klienta (chyba że pula wątków serwera ma tylko jeden wątek).
Oprócz wywołań zwrotnych synchronicznych wywołania oneway
z klienta jednowątkowego mogą być obsługiwane równolegle przez serwer z wieloma wątkami w zbiorze wątków, ale tylko wtedy, gdy te wywołania oneway
są wykonywane na różnych interfejsach. Połączenia oneway
w ramach tego samego interfejsu są zawsze serializowane.
Uwaga: zdecydowanie zalecamy, aby funkcje serwera zwracały wartość, gdy tylko wywołają funkcję wywołania zwrotnego.
Na przykład (w 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 };
Model wątkowania klienta
Model wątków na kliencie różni się w przypadku wywołań nieblokujących (funkcji oznaczonych słowem kluczowym oneway
) i wywołań blokujących (funkcji, które nie mają określonego słowa kluczowego oneway
).
Blokuj połączenia
W przypadku blokowania połączeń klient blokuje je do momentu wystąpienia jednego z tych zdarzeń:
- Wystąpił błąd transportu; obiekt
Return
zawiera stan błędu, który można pobrać za pomocą funkcjiReturn::isOk()
. - Implementacja serwera wywołuje wywołanie zwrotne (jeśli było ono określone).
- Implementacja serwera zwraca wartość (jeśli nie było parametru callback).
W przypadku powodzenia funkcja wywołania zwrotnego przekazana przez klienta jako argument jest zawsze wywoływana przez serwer przed zwróceniem samej funkcji. Wywołanie zwrotne jest wykonywane w tym samym wątku, w którym zostało wywołane, dlatego implementatorzy muszą zachować ostrożność podczas blokowania blokad podczas wywoływania funkcji (i unikać ich całkowicie, jeśli to możliwe). Funkcja bez instrukcji generates
lub słowa kluczowego oneway
nadal blokuje; klient blokuje do momentu, gdy serwer zwróci obiekt Return<void>
.
Połączenia jednokierunkowe
Jeśli funkcja jest oznaczona jako oneway
, klient zwraca wartość natychmiast i nie czeka, aż serwer zakończy wywołanie funkcji. Na pierwszy rzut oka (i w ujęciu zbiorczym) oznacza to, że wywołanie funkcji zajmuje połowę czasu, ponieważ wykonuje połowę kodu, ale podczas pisania implementacji, które są wrażliwe na wydajność, ma to pewne konsekwencje dla harmonogramu. Zazwyczaj wywołanie jednostronne powoduje, że wywołujący pozostaje w planie, natomiast wywołanie synchroniczne powoduje natychmiastowe przeniesienie wywołującego do wywoływanego procesu. Jest to optymalizacja skuteczności w programie do tworzenia formularzy. W przypadku usług, w których wywołanie jednokierunkowe musi być wykonywane w ramach procesu docelowego z wysokim priorytetem, można zmienić harmonogram usługi odbierającej. W języku C++ korzystanie z metody libhidltransport
setMinSchedulerPolicy
z użyciem priorytetów i zasad harmonogramu określonych w sched.h
zapewnia, że wszystkie wywołania usługi są wykonywane co najmniej zgodnie z ustalonymi zasadami i priorytetami harmonogramu.