Os workers persistentes podem acelerar a criação. Se você tiver ações repetidas na criação que tenham um custo de inicialização alto ou que se beneficiariam do armazenamento em cache entre ações, implemente seu próprio worker persistente para realizar essas ações.
O servidor do Bazel se comunica com o worker usando stdin/stdout. Ele
oferece suporte ao uso de buffers de protocolo ou strings JSON.
A implementação do worker tem duas partes:
- O worker.
- A regra que usa o worker.
Como criar o worker
Um worker persistente atende a alguns requisitos:
- Ele lê
WorkRequests
do seu
stdin. - Ele grava
WorkResponses
(e somente
WorkResponses) nostdout. - Ele aceita a
--persistent_workerflag. O wrapper precisa reconhecer a--persistent_workerflag de linha de comando e só se tornar persistente se essa flag for transmitida. Caso contrário, ele precisa fazer uma compilação única e sair.
Se o programa atender a esses requisitos, ele poderá ser usado como um worker persistente.
Solicitações de trabalho
Um WorkRequest contém uma lista de argumentos para o worker, uma lista de
pares de caminho-resumo que representam as entradas que o worker pode acessar (isso não é
obrigatório, mas você pode usar essas informações para armazenamento em cache) e um ID de solicitação, que é 0
para workers de modo único.
OBSERVAÇÃO: embora a especificação do buffer de protocolo use "snake case" (request_id),
o protocolo JSON use "camel case" (requestId). Este documento usa camel case
nos exemplos JSON, mas snake case ao falar sobre o campo, independentemente do
protocolo.
{
"arguments" : ["--some_argument"],
"inputs" : [
{ "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
{ "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
],
"requestId" : 12
}
O campo opcional verbosity pode ser usado para solicitar uma saída de depuração extra
do worker. Cabe ao worker decidir o que e como gerar a saída. Valores mais altos
indicam uma saída mais detalhada. Transmitir a flag --worker_verbose para
o Bazel define o campo verbosity como 10, mas valores menores ou maiores podem ser usados
manualmente para diferentes quantidades de saída.
O campo sandbox_dir opcional é usado apenas por workers que oferecem suporte a
sandbox multiplex.
Respostas de trabalho
Um WorkResponse contém um ID de solicitação, um código de saída zero ou diferente de zero e uma
mensagem de saída que descreve os erros encontrados no processamento ou na execução
da solicitação. Um worker precisa capturar o stdout e o stderr de qualquer ferramenta que ele
chame e informá-los pelo WorkResponse. Gravá-lo no stdout de
o processo do worker não é seguro, porque interfere no protocolo do worker.
Gravá-lo no stderr do processo do worker é seguro, mas o resultado é
coletado em um arquivo de registro por worker, em vez de ser atribuído a ações individuais.
{
"exitCode" : 1,
"output" : "Action failed with the following message:\nCould not find input
file \"/path/to/my/file/1\"",
"requestId" : 12
}
De acordo com a norma para protobufs, todos os campos são opcionais. No entanto, o Bazel exige que
o WorkRequest e o WorkResponse correspondente tenham o mesmo ID de solicitação
. Portanto, o ID de solicitação precisa ser especificado se for diferente de zero. Este é um válido
WorkResponse.
{
"requestId" : 12,
}
Um request_id de 0 indica uma solicitação "singleplex", usada quando essa solicitação
não pode ser processada em paralelo com outras. O servidor garante que
um determinado worker receba solicitações com apenas request_id 0 ou apenas
request_id maior que zero. As solicitações de modo único são enviadas em série, por
exemplo, se o servidor não enviar outra solicitação até receber uma
resposta (exceto para solicitações de cancelamento, consulte abaixo).
Observações
- Cada buffer de protocolo é precedido pelo comprimento no formato
varint(consulteMessageLite.writeDelimitedTo(). - As solicitações e respostas JSON não são precedidas por um indicador de tamanho.
- As solicitações JSON mantêm a mesma estrutura do protobuf, mas usam JSON padrão e camel case para todos os nomes de campo.
- Para manter as mesmas propriedades de compatibilidade com versões anteriores e futuras do protobuf, os workers JSON precisam tolerar campos desconhecidos nessas mensagens, e usar os padrões do protobuf para valores ausentes.
- O Bazel armazena solicitações como protobufs e as converte em JSON usando o formato JSON do protobuf.
Cancelamento
Os workers podem permitir que as solicitações de trabalho sejam canceladas antes de serem concluídas.
Isso é particularmente útil em conexão com a execução dinâmica, em que a execução local
pode ser interrompida regularmente por uma execução remota mais rápida. Para permitir
o cancelamento, adicione supports-worker-cancellation: 1 ao
execution-requirements campo (consulte abaixo) e defina a
--experimental_worker_cancellation flag.
Uma solicitação de cancelamento é um WorkRequest com o campo cancel definido (e
da mesma forma, uma resposta de cancelamento é um WorkResponse com o campo was_cancelled
definido). O único outro campo que precisa estar em uma solicitação ou resposta de cancelamento é request_id, que indica qual solicitação cancelar. O request_id
campo será 0 para workers de modo único ou o request_id diferente de 0 de um
enviado WorkRequest para workers multiplex. O servidor pode enviar solicitações de cancelamento
para solicitações que o worker já respondeu. Nesse caso, a solicitação de cancelamento
precisa ser ignorada.
Cada mensagem não cancelada WorkRequest precisa ser respondida exatamente uma vez, tenha sido cancelada ou
não. Depois que o servidor enviar uma solicitação de cancelamento, o worker poderá
responder com um WorkResponse com o request_id definido e o was_cancelled
campo definido como verdadeiro. O envio de um WorkResponse normal também é aceito, mas os
output e exit_code campos serão ignorados.
Depois que uma resposta for enviada para um WorkRequest, o worker não poderá tocar nos
arquivos no diretório de trabalho. O servidor pode limpar os arquivos,
incluindo os temporários.
Como criar a regra que usa o worker
Você também precisa criar uma regra que gere ações a serem realizadas pelo worker. Criar uma regra do Starlark que usa um worker é como criar qualquer outra regra.
Além disso, a regra precisa conter uma referência ao próprio worker, e há alguns requisitos para as ações que ela produz.
Como se referir ao worker
A regra que usa o worker precisa conter um campo que se refere ao próprio worker
. Portanto, você precisa criar uma instância de uma regra \*\_binary para definir
o worker. Se o worker for chamado de MyWorker.Java, esta poderá ser a
regra associada:
java_binary(
name = "worker",
srcs = ["MyWorker.Java"],
)
Isso cria o rótulo "worker", que se refere ao binário do worker. Em seguida, defina uma regra que use o worker. Essa regra precisa definir um atributo que se refere ao binário do worker.
Se o binário do worker criado estiver em um pacote chamado "work", que está no nível superior da criação, esta poderá ser a definição do atributo:
"worker": attr.label(
default = Label("//work:worker"),
executable = True,
cfg = "exec",
)
cfg = "exec" indica que o worker precisa ser criado para ser executado na sua
plataforma de execução, e não na plataforma de destino (ou seja, o worker é usado
como ferramenta durante a criação).
Requisitos de ação de trabalho
A regra que usa o worker cria ações para o worker realizar. Essas ações têm alguns requisitos.
O campo "arguments". Ele recebe uma lista de strings, todas, exceto a última, que são argumentos transmitidos ao worker na inicialização. O último elemento na lista "arguments" é um
flag-file(@-precedido) argumento. Os workers leem os argumentos do arquivo de flag especificado por WorkRequest. Sua regra pode gravar argumentos de não inicialização para o worker nesse arquivo de flag.O campo "execution-requirements", que recebe um dicionário contendo
"supports-workers" : "1","supports-multiplex-workers" : "1", ou ambos.Os campos "arguments" e "execution-requirements" são obrigatórios para todas as ações enviadas aos workers. Além disso, as ações que precisam ser executadas por workers JSON precisam incluir
"requires-worker-protocol" : "json"no campo de requisitos de execução."requires-worker-protocol" : "proto"também é um requisito de execução válido, embora não seja necessário para workers proto, já que eles são o padrão.Também é possível definir um
worker-key-mnemonicnos requisitos de execução. Isto pode ser útil se você estiver reutilizando o executável para vários tipos de ação e quiser distinguir as ações por esse worker.Os arquivos temporários gerados durante a ação precisam ser salvos no diretório do worker. Isso permite o uso de sandbox.
Considerando uma definição de regra com o atributo "worker" descrito acima, além
de um atributo "srcs" que representa as entradas, um atributo "output" que
representa as saídas e um atributo "args" que representa os argumentos de inicialização do worker,
a chamada para ctx.actions.run pode ser:
ctx.actions.run(
inputs=ctx.files.srcs,
outputs=[ctx.outputs.output],
executable=ctx.executable.worker,
mnemonic="someMnemonic",
execution_requirements={
"supports-workers" : "1",
"requires-worker-protocol" : "json"},
arguments=ctx.attr.args + ["@flagfile"]
)
Para outro exemplo, consulte Implementar workers persistentes.
Exemplos
A base de código do Bazel usa workers do compilador Java, além de um worker JSON de exemplo usado nos nossos testes de integração.
Você pode usar o scaffolding deles para transformar qualquer ferramenta baseada em Java em um worker transmitindo o callback correto.
Para ver um exemplo de uma regra que usa um worker, confira o teste de integração do worker do Bazel.
Colaboradores externos implementaram workers em vários idiomas. Confira as implementações poliglota de workers persistentes do Bazel. Você pode encontrar muitos outros exemplos no GitHub.