Modelos de rosqueamento

Os métodos marcados como de oneway não bloqueiam. Para métodos não marcados como oneway , a chamada de método de um 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 de chamada extras são descartadas e registradas como erros. Se um método deve retornar valores por meio de retorno de chamada e não chamar seu retorno de chamada, isso é registrado como um erro e relatado como um erro de transporte para o cliente.

Threads no modo de passagem

No modo de passagem, a maioria das chamadas é síncrona. 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 encadernados

Para atender chamadas RPC de entrada (incluindo retornos de chamada assíncronos de HALs para usuários HAL) e notificações de morte, um pool de encadeamentos é associado a cada processo que usa HIDL. Se um único processo implementa várias interfaces HIDL e/ou manipuladores de notificação de morte, seu pool de encadeamentos é compartilhado entre todos eles. Quando um processo recebe uma chamada de método 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 bloqueia até que um esteja disponível.

Se o servidor tiver apenas um encadeamento, as chamadas no servidor serão concluídas em ordem. Um servidor com mais de um encadeamento pode concluir chamadas fora de ordem, mesmo que o cliente tenha apenas um encadeamento. No entanto, para um determinado objeto de interface, as chamadas oneway são garantidas para serem ordenadas (consulte Modelo de encadeamento do servidor ). Para um servidor multithread que hospeda várias interfaces, chamadas oneway para interfaces diferentes podem ser processadas simultaneamente umas com as outras ou com 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, em seguida, 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 torna possível ter um único servidor encadeado 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 em um processo (C) e, em seguida, o processo (C) chama de volta em (A), ele não pode ser servido no thread original em (A).

Modelo de segmentação do servidor

Exceto para o modo de passagem, 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 de entrada. Esses encadeamentos são o conjunto de encadeamentos 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 encadeamento no pool de encadeamentos, ele poderá receber chamadas de entrada simultâneas em qualquer uma de suas interfaces (em C++, isso significa que os dados compartilhados devem ser cuidadosamente bloqueados).

As chamadas unidirecionais na mesma interface são serializadas. Se um cliente multi-thread chamar method1 e method2 na interface IFoo e method3 na interface IBar , method1 e method2 sempre serão serializados, mas method3 pode ser executado em paralelo com method1 e method2 .

Um único segmento de execução do cliente pode causar execução simultânea em um servidor com vários segmentos de duas maneiras:

  • chamadas oneway não são bloqueadas. Se uma chamada oneway for executada e uma não oneway for chamada, o servidor poderá executar a chamada oneway e a não oneway simultaneamente.
  • Os métodos de servidor que retornam 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 executado após a chamada do retorno de chamada pode ser executado simultaneamente, com o servidor lidando com as chamadas subsequentes do cliente. Isso inclui código na função do servidor e destruidores automáticos que são executados no final da função. Se o servidor tiver mais de um encadeamento em seu pool de encadeamentos, surgirão problemas de simultaneidade mesmo se as chamadas forem provenientes de apenas um único encadeamento do cliente. (Se qualquer HAL servido por um processo precisar de vários encadeamentos, todos os HALs terão vários encadeamentos porque o pool de encadeamentos é compartilhado por processo.)

Assim que o servidor chama o retorno de chamada fornecido, o transporte pode chamar o retorno de chamada implementado no cliente e desbloquear o cliente. O cliente prossegue em paralelo com o que a implementação do servidor fizer depois de chamar o retorno de chamada (que pode incluir destruidores em execução). O código na função do servidor após o retorno de chamada não está mais bloqueando o cliente (desde que o pool de threads do servidor tenha threads suficientes para lidar com as chamadas recebidas), mas pode ser executado simultaneamente com chamadas futuras do cliente (a menos que o pool de threads do servidor tenha apenas um thread ).

Além dos retornos de chamada síncronos, as chamadas oneway de um cliente de encadeamento único podem ser tratadas simultaneamente por um servidor com vários encadeamentos em seu pool de encadeamentos, mas somente se essas chamadas oneway forem executadas em interfaces diferentes. chamadas oneway na mesma interface são sempre serializadas.

Observação: recomendamos 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 segmentação do cliente

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

Bloqueando chamadas

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

  • 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 retorno de chamada (se houver).
  • A implementação do servidor retorna um valor (se não houver um 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 encadeamento em que a chamada de função é feita, portanto, os implementadores devem ter cuidado ao manter os bloqueios durante as 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 só de ida

Quando uma função é marcada como oneway , o cliente retorna imediatamente e não espera que o servidor complete sua chamada de função. Na superfície (e no agregado), isso significa que a chamada da 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 unidirecional 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 chamador para o processo chamado. Esta é uma otimização de desempenho no fichário. Para serviços em que a chamada unidirecional deve ser executada no processo de destino com alta prioridade, a política de agendamento do serviço receptor pode ser alterada. Em C++, usar o método libhidltransport da setMinSchedulerPolicy 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.