Modelos de rosqueamento

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 chamada oneway for executada e, em seguida, uma chamada não oneway for chamada, o servidor poderá executar a chamada oneway e a chamada não oneway 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 com Return::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.