Linha de execução do modelo

Os métodos marcados como oneway não são bloqueados. Para métodos não marcados como oneway, a chamada de método de um cliente é bloqueada até que o servidor tenha concluído a execução ou tenha chamado um callback síncrono (o que ocorrer primeiro). As implementações de método do servidor podem chamar no máximo um callback síncrono. As chamadas de callback extras são descartadas e registradas como erros. Se um método precisar retornar valores por callback e não chamar o callback, isso será registrado como um erro e informado como um erro de transporte ao cliente.

Linhas de execução no modo de passagem

No modo de transferência, a maioria das chamadas é síncrona. No entanto, para preservar o comportamento esperado de que as chamadas oneway não bloqueiem o cliente, uma linha de execução é criada para cada processo. Para mais detalhes, consulte a visão geral de HIDL.

Linhas de execução em HALs de vinculação

Para atender chamadas RPC recebidas (incluindo callbacks assíncronos de HALs para usuários de HAL) e notificações de morte, um threadpool é associado a cada processo que usa HIDL. Se um único processo implementar várias interfaces HIDL e/ou gerenciadores de notificação de morte, o threadpool dele será compartilhado entre todos eles. Quando um processo recebe uma chamada de método de entrada de um cliente, ele escolhe uma linha de execução disponível do conjunto de linhas de execução e executa a chamada nessa linha de execução. Se nenhuma linha de execução sem custo financeiro estiver disponível, ela será bloqueada até que uma esteja.

Se o servidor tiver apenas uma linha de execução, as chamadas para o servidor serão concluídas na ordem. Um servidor com mais de uma linha de execução pode concluir chamadas fora de ordem, mesmo que o cliente tenha apenas uma linha de execução. No entanto, para um determinado objeto de interface, as chamadas oneway são garantidas para serem ordenadas (consulte Modelo de linha de execução do servidor). Para um servidor multithread que hospeda várias interfaces, as chamadas oneway para interfaces diferentes podem ser processadas simultaneamente ou outras chamadas de bloqueio.

Várias chamadas aninhadas são enviadas na mesma linha de execução do hwbinder. Por exemplo, se um processo (A) faz uma chamada síncrona de uma linha de execução do hwbinder para o processo (B), e o processo (B) faz uma chamada síncrona de volta para o processo (A), a chamada é executada na linha de execução original do hwbinder em (A), que é bloqueada na chamada original. Essa otimização possibilita que um servidor de linha única seja capaz de processar chamadas aninhadas, mas não se estende aos casos em que as chamadas passam por outra sequência de chamadas IPC. Por exemplo, se o processo (B) fez uma chamada binder/vndbinder que chamou um processo (C) e, em seguida, o processo (C) chamou de volta para (A), ele não pode ser atendido na linha de execução original em (A).

Modelo de encadeamento do servidor

Exceto no modo de transferência, as implementações do servidor de interfaces HIDL ficam em um processo diferente do cliente e precisam de uma ou mais linhas de execução aguardando chamadas de método recebidas. Essas linhas de execução são o pool de linhas de execução do servidor. O servidor pode decidir quantas linhas de execução ele quer executar no pool e pode usar um tamanho de pool de linhas de execução de um para serializar todas as chamadas nas interfaces. Se o servidor tiver mais de uma linha de execução no pool de linhas de execução, ele poderá receber chamadas entrantes simultâneas em qualquer uma das interfaces. Em C++, isso significa que os dados compartilhados precisam ser bloqueados com cuidado.

As chamadas unidirecionais para a mesma interface são serializadas. Se um cliente multithread chamar method1 e method2 na interface IFoo e method3 na interface IBar, method1 e method2 serão sempre serializados, mas method3 poderá ser executado em paralelo com method1 e method2.

Uma única linha de execução do cliente pode causar execução simultânea em um servidor com várias linhas de duas maneiras:

  • As chamadas para oneway não são bloqueadas. Se uma chamada oneway for executada e uma chamada não oneway for chamada, o servidor poderá executar a chamada oneway e a chamada não oneway simultaneamente.
  • Os métodos do servidor que transmitem dados de volta com callbacks síncronos podem desbloquear o cliente assim que o callback é chamado pelo servidor.

Na segunda maneira, qualquer código na função do servidor que é executado após a chamada do callback pode ser executado simultaneamente, com o servidor processando chamadas posteriores do cliente. Isso inclui o código na função do servidor e os destrutores automáticos que são executados no final da função. Se o servidor tiver mais de uma linha de execução no thread pool, problemas de simultaneidade vão surgir mesmo que as chamadas estejam chegando de apenas uma única linha de execução do cliente. Se qualquer HAL atendido por um processo precisar de várias linhas de execução, todas as HALs terão várias linhas de execução porque o pool de linhas de execução é compartilhado por processo.

Assim que o servidor chamar o callback fornecido, o transporte poderá chamar o callback implementado no cliente e desbloqueá-lo. O cliente prossegue em paralelo com o que a implementação do servidor faz depois de chamar o callback, o que pode incluir a execução de destrutores. O código na função do servidor depois que o callback não estiver mais bloqueando o cliente (desde que o pool de linhas de execução do servidor tenha linhas de execução suficientes para processar chamadas de entrada), mas pode ser executado simultaneamente com chamadas futuras do cliente, a menos que o pool de linhas de execução do servidor tenha apenas uma linha de execução.

Além de callbacks síncronos, as chamadas oneway de um cliente de linha de execução única podem ser processadas simultaneamente por um servidor com várias linhas de execução no pool de linhas de execução, mas somente se essas chamadas oneway forem executadas em interfaces diferentes. As chamadas oneway na mesma interface são sempre serializadas.

Observação:recomendamos que as funções do servidor sejam retornadas assim que chamarem a função de callback.

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 is called,
    // and the client resumes execution.
    ...
    return Void(); // is basically a no-op
};

Modelo de encadeamento do cliente

O modelo de linhas de execução no cliente difere entre chamadas não bloqueantes (funções marcadas com a palavra-chave oneway) e chamadas de bloqueio (funções que não têm a palavra-chave oneway especificada).

Bloquear chamadas

Para chamadas de bloqueio, o cliente bloqueia até que uma das seguintes situações ocorra:

  • Ocorre um 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 callback (se houver um).
  • A implementação do servidor retorna um valor (se não houver um parâmetro de callback).

Em caso de sucesso, a função de callback que o cliente transmite como um argumento é sempre chamada pelo servidor antes que a própria função seja retornada. O callback é executado na mesma linha de execução em que a chamada de função é feita. Portanto, os implementadores precisam ter cuidado ao manter bloqueios durante as chamadas de função e evitá-los por completo quando possível. Uma função sem uma instrução generates ou uma palavra-chave oneway ainda está bloqueando. O cliente é bloqueado até que o servidor retorne um objeto Return<void>.

Chamadas unidirecionais

Quando uma função é marcada como oneway, o cliente retorna imediatamente e não espera que o servidor conclua a invocação da chamada de função. Na superfície (e no agregado), 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 à performance, isso tem algumas implicações de programação. Normalmente, usar uma chamada unidirecional faz com que o autor da chamada continue sendo programado, enquanto usar uma chamada síncrona normal faz com que o programador seja transferido imediatamente do autor da chamada para o processo de chamada. Essa é uma otimização de desempenho no binder. Para serviços em que a chamada unidirecional precisa ser executada no processo de destino com alta prioridade, a política de programação do serviço de recebimento pode ser alterada. Em C++, o uso do método setMinSchedulerPolicy de libhidltransport com as prioridades e políticas do programador definidas em sched.h garante que todas as chamadas para o serviço sejam executadas pelo menos na política e prioridade de programação definidas.