Métodos marcados como oneway
não bloqueiam. Para métodos não marcados como oneway
, a chamada de método do cliente será bloqueada até que o servidor conclua a execução ou chame um retorno de chamada síncrono (o que ocorrer primeiro). As implementações de métodos de servidor podem chamar no máximo um retorno de chamada síncrono; chamadas de retorno extras são descartadas e registradas como erros. Se um método deveria retornar valores via retorno de chamada e não chamar seu retorno de chamada, isso será registrado como um erro e relatado como um erro de transporte ao cliente.
Threads em modo de passagem
No modo passthrough, a maioria das chamadas são síncronas. No entanto, para preservar o comportamento pretendido de que as chamadas oneway
não bloqueiem o cliente, um thread é criado para cada processo. Para obter detalhes, consulte a visão geral do HIDL .
Threads em HALs binderizados
Para atender chamadas RPC de entrada (incluindo retornos de chamada assíncronos de HALs para usuários HAL) e notificações de encerramento, um threadpool é associado a cada processo que usa HIDL. Se um único processo implementa múltiplas interfaces HIDL e/ou manipuladores de notificação de morte, seu pool de threads é compartilhado entre todos eles. Quando um processo recebe uma chamada de método de entrada de um cliente, ele escolhe um thread livre do pool de threads e executa a chamada nesse thread. Se nenhum thread livre estiver disponível, ele será bloqueado até que um esteja disponível.
Se o servidor tiver apenas um thread, as chamadas para o servidor serão concluídas em ordem. Um servidor com mais de um thread pode concluir chamadas fora de ordem, mesmo que o cliente tenha apenas um thread. No entanto, para um determinado objeto de interface, é garantido que as chamadas oneway
sejam ordenadas (consulte Modelo de threading do servidor ). Para um servidor multithread que hospeda múltiplas interfaces, chamadas oneway
para diferentes interfaces podem ser processadas simultaneamente entre si ou outras chamadas de bloqueio.
Várias chamadas aninhadas serão enviadas no mesmo thread hwbinder. Por exemplo, se um processo (A) fizer uma chamada síncrona de um thread hwbinder para o processo (B) e então o processo (B) fizer uma chamada síncrona de volta para o processo (A), a chamada será executada no thread hwbinder original em (A) que está bloqueado na chamada original. Essa otimização possibilita ter um único servidor threaded capaz de lidar com chamadas aninhadas, mas não se estende aos casos em que as chamadas trafegam por outra sequência de chamadas IPC. Por exemplo, se o processo (B) fez uma chamada de binder/vndbinder que chamou um processo (C) e depois o processo (C) chama de volta para (A), ele não pode ser servido no thread original em (A).
Modelo de threading de servidor
Exceto pelo modo passthrough, as implementações de servidor de interfaces HIDL vivem em um processo diferente do cliente e precisam de um ou mais threads aguardando chamadas de método recebidas. Esses threads são o threadpool do servidor; o servidor pode decidir quantos threads deseja executar em seu pool de threads e pode usar um tamanho de pool de threads de um para serializar todas as chamadas em suas interfaces. Se o servidor tiver mais de um thread no threadpool, ele poderá receber chamadas simultâneas em qualquer uma de suas interfaces (em C++, isso significa que os dados compartilhados devem ser cuidadosamente bloqueados).
Chamadas unidirecionais na mesma interface são serializadas. Se um cliente multithread chamar method1
e method2
na interface IFoo
e method3
na interface IBar
, method1
e method2
sempre serão serializados, mas method3
poderá ser executado em paralelo com method1
e method2
.
Um único thread de execução do cliente pode causar execução simultânea em um servidor com vários threads de duas maneiras:
- chamadas
oneway
não são bloqueadas. Se uma chamadaoneway
for executada e, em seguida, uma chamada nãooneway
for chamada, o servidor poderá executar a chamadaoneway
e a chamada nãooneway
simultaneamente. - Os métodos de servidor que transmitem dados com retornos de chamada síncronos podem desbloquear o cliente assim que o retorno de chamada for chamado do servidor.
Para a segunda maneira, qualquer código na função do servidor que seja executado após o retorno de chamada ser chamado pode ser executado simultaneamente, com o servidor lidando com chamadas subsequentes do cliente. Isso inclui código na função do servidor e destruidores automáticos executados no final da função. Se o servidor tiver mais de um encadeamento em seu conjunto de encadeamentos, surgirão problemas de simultaneidade mesmo se as chamadas vierem de apenas um único encadeamento do cliente. (Se qualquer HAL servido por um processo precisar de vários threads, todos os HALs terão vários threads porque o conjunto de threads é compartilhado por processo.)
Assim que o servidor chamar o retorno de chamada fornecido, o transporte poderá chamar o retorno de chamada implementado no cliente e desbloquear o cliente. O cliente prossegue em paralelo com tudo o que a implementação do servidor faz depois de chamar o retorno de chamada (que pode incluir a execução de destruidores). O código na função do servidor após o retorno de chamada não está mais bloqueando o cliente (desde que o threadpool do servidor tenha threads suficientes para lidar com chamadas recebidas), mas pode ser executado simultaneamente com chamadas futuras do cliente (a menos que o threadpool do servidor tenha apenas um thread ).
Além dos retornos de chamada síncronos, as chamadas oneway
de um cliente de thread único podem ser tratadas simultaneamente por um servidor com vários threads em seu pool de threads, mas somente se essas chamadas oneway
forem executadas em interfaces diferentes. chamadas oneway
na mesma interface são sempre serializadas.
Nota: Recomendamos fortemente que as funções do servidor retornem assim que chamarem a função de retorno de chamada.
Por exemplo (em 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 };
Modelo de threading do cliente
O modelo de threading no cliente difere entre chamadas sem bloqueio (funções marcadas com a palavra-chave oneway
) e chamadas de bloqueio (funções que não possuem a palavra-chave oneway
especificada).
Bloqueando chamadas
Para bloquear chamadas, o cliente bloqueia até que aconteça uma das seguintes situações:
- Ocorre erro de transporte; o objeto
Return
contém um estado de erro que pode ser recuperado comReturn::isOk()
. - A implementação do servidor chama o retorno de chamada (se houver).
- A implementação do servidor retorna um valor (se não houver parâmetro de retorno de chamada).
Em caso de sucesso, a função de retorno de chamada que o cliente passa como argumento é sempre chamada pelo servidor antes que a própria função retorne. O retorno de chamada é executado no mesmo thread em que a chamada de função é feita, portanto, os implementadores devem ter cuidado ao manter bloqueios durante chamadas de função (e evitá-los completamente quando possível). Uma função sem uma instrução generates
ou uma palavra-chave oneway
ainda está bloqueando; o cliente bloqueia até que o servidor retorne um objeto Return<void>
.
Chamadas unidirecionais
Quando uma função é marcada oneway
, o cliente retorna imediatamente e não espera que o servidor conclua sua invocação de chamada de função. Superficialmente (e de forma agregada), isso significa que a chamada de função leva metade do tempo porque está executando metade do código, mas ao escrever implementações que são sensíveis ao desempenho, isso tem algumas implicações de agendamento. Normalmente, o uso de uma chamada unilateral faz com que o chamador continue a ser agendado, enquanto o uso de uma chamada síncrona normal faz com que o agendador seja transferido imediatamente do processo chamador para o processo chamado. Esta é uma otimização de desempenho no fichário. Para serviços onde a chamada unidirecional deve ser executada no processo alvo com alta prioridade, a política de escalonamento do serviço receptor pode ser alterada. Em C++, usar o método setMinSchedulerPolicy
de libhidltransport
com as prioridades e políticas do agendador definidas em sched.h
garante que todas as chamadas para o serviço sejam executadas pelo menos na política e prioridade de agendamento definidas.