Los trabajadores persistentes pueden hacer que tu compilación sea más rápida. Si tienes acciones repetidas en tu compilación que tienen un costo de inicio alto o se beneficiarían del almacenamiento en caché de acciones cruzadas, te recomendamos que implementes tu propio trabajador persistente para realizar estas acciones.
El servidor de Bazel se comunica con el trabajador mediante stdin
/stdout
. Admite el uso de búferes de protocolo o strings JSON.
La implementación trabajadora tiene dos partes:
- El trabajador.
- La regla que usa el worker.
Convertir al trabajador
Un trabajador persistente cumple algunos requisitos:
- Lee WorkRequests desde su
stdin
. - Escribe WorkResponses (y solo
WorkResponse
) en sustdout
. - Acepta la marca
--persistent_worker
. El wrapper debe reconocer la marca de línea de comandos--persistent_worker
y solo ser persistente si se pasa esa marca. De lo contrario, debe realizar una compilación en un solo intento y salir.
Si tu programa cumple con estos requisitos, se puede usar como un trabajador persistente.
Solicitudes de trabajo
Un WorkRequest
contiene una lista de argumentos para el trabajador, una lista de pares de resumen de ruta que representan las entradas a las que puede acceder el trabajador (esto no se aplica, pero puedes usar esta información para el almacenamiento en caché) y un ID de solicitud, que es 0 para trabajadores de un soloplex.
NOTA: Si bien la especificación del búfer de protocolo usa "snake case" (request_id
), el protocolo JSON usa "camel case" (requestId
). En este documento, se utiliza este formato en los ejemplos de JSON, pero se utiliza snake case cuando se hace referencia al campo, sin importar el protocolo.
{
"arguments" : ["--some_argument"],
"inputs" : [
{ "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
{ "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
],
"requestId" : 12
}
Se puede usar el campo opcional verbosity
para solicitar un resultado de depuración adicional al trabajador. Depende completamente del trabajador qué y cómo generar. Los valores más altos indican un resultado más detallado. Pasar la marca --worker_verbose
a Bazel establece el campo verbosity
en 10, pero los valores más pequeños o más grandes se pueden usar de forma manual para diferentes cantidades de resultados.
Solo los trabajadores que admiten la zona de pruebas multiplex usan el campo opcional sandbox_dir
.
Respuestas de trabajo
Una WorkResponse
contiene un ID de solicitud, un código de salida cero o distinto de cero, y una string de salida que describe los errores encontrados en el procesamiento o la ejecución de la solicitud. El campo output
contiene una descripción breve; los registros completos se pueden escribir en el stderr
del trabajador. Debido a que los trabajadores solo pueden escribir WorkResponses
en stdout
, es común que redireccionen el stdout
de cualquier herramienta que usa a stderr
.
{
"exitCode" : 1,
"output" : "Action failed with the following message:\nCould not find input
file \"/path/to/my/file/1\"",
"requestId" : 12
}
Según la norma de los protobufs, todos los campos son opcionales. Sin embargo, Bazel requiere
que la WorkRequest
y el WorkResponse
correspondiente tengan el mismo
ID de solicitud, por lo que este debe especificarse si no es cero. Este es un WorkResponse
válido.
{
"requestId" : 12,
}
Un request_id
de 0 indica una solicitud de "singleplex", que se usa cuando esta solicitud no se puede procesar en paralelo con otras solicitudes. El servidor garantiza que un trabajador determinado reciba solicitudes con un valor de request_id
0 o un valor de request_id
mayor que cero. Las solicitudes de singleplex se envían en serie, por ejemplo, si el servidor no envía otra solicitud hasta que haya recibido una respuesta (excepto para las solicitudes de cancelación, consulta a continuación).
Notes
- Cada búfer de protocolo está precedido por su longitud en formato
varint
(consultaMessageLite.writeDelimitedTo()
. - Las solicitudes y respuestas de JSON no están precedidas por un indicador de tamaño.
- Las solicitudes JSON mantienen la misma estructura que el protobuf, pero usan JSON estándar y usan mayúsculas mediales para todos los nombres de campo.
- Para mantener las mismas propiedades de retrocompatibilidad que protobuf, los trabajadores de JSON deben tolerar campos desconocidos en estos mensajes y usar los valores predeterminados de protobuf para valores faltantes.
- Bazel almacena solicitudes como protobufs y las convierte a JSON con el formato JSON de protobuf.
Cancelación
De manera opcional, los trabajadores pueden permitir que se cancelen las solicitudes de trabajo antes de que finalicen.
Esto es muy útil en relación con la ejecución dinámica, en la que la ejecución
local puede interrumpirse a menudo debido a una ejecución remota más rápida. Para permitir la cancelación, agrega supports-worker-cancellation: 1
al campo execution-requirements
(consulta a continuación) y configura la marca --experimental_worker_cancellation
.
Una solicitud de cancelación es un WorkRequest
con el campo cancel
configurado (y, de manera similar, una respuesta de cancelación es un WorkResponse
con el campo was_cancelled
configurado). El único otro campo que debe estar en una solicitud de cancelación o respuesta de cancelación es request_id
, que indica qué solicitud cancelar. El campo request_id
será 0 para trabajadores de un soloplex o el request_id
que no sea 0 de un WorkRequest
enviado anteriormente para trabajadores multiplex. El servidor puede enviar solicitudes de cancelación para solicitudes que el trabajador ya respondió, en cuyo caso la solicitud de cancelación debe ignorarse.
Cada mensaje WorkRequest
no cancelado debe responderse exactamente una vez, sin importar si se canceló o no. Una vez que el servidor haya enviado una solicitud de cancelación, el trabajador puede responder con una WorkResponse
con el request_id
configurado y el campo was_cancelled
configurado como verdadero. También se acepta el envío de un WorkResponse
normal, pero los campos output
y exit_code
se ignorarán.
Una vez que se envió una respuesta para un WorkRequest
, el trabajador no debe tocar los archivos en su directorio de trabajo. El servidor puede limpiar los archivos, incluidos los temporales.
Crea la regla que usa el trabajador
También deberás crear una regla que genere acciones que debe realizar el trabajador. Crear una regla de Starlark que use un trabajador es igual que crear cualquier otra regla.
Además, la regla debe contener una referencia al trabajador en sí y existen algunos requisitos para las acciones que produce.
Cómo hacer referencia al trabajador
La regla que usa el trabajador debe contener un campo que haga referencia al trabajador en sí, por lo que deberás crear una instancia de una regla \*\_binary
para definir tu trabajador. Si tu trabajador se llama MyWorker.Java
, esta podría ser la regla asociada:
java_binary(
name = "worker",
srcs = ["MyWorker.Java"],
)
Esto crea la etiqueta “trabajador”, que hace referencia al objeto binario del trabajador. Luego, definirás una regla que use el trabajador. Esta regla debe definir un atributo que haga referencia al objeto binario del trabajador.
Si el objeto binario de trabajador que compilaste está en un paquete llamado "work", que se encuentra en el nivel superior de la compilación, esta podría ser la definición del atributo:
"worker": attr.label(
default = Label("//work:worker"),
executable = True,
cfg = "exec",
)
cfg = "exec"
indica que el trabajador debe compilarse para ejecutarse en tu plataforma de ejecución en lugar de en la plataforma de destino (es decir, el trabajador se usa como herramienta durante la compilación).
Requisitos para las acciones laborales
La regla que usa el trabajador crea las acciones que debe realizar el trabajador. Estas acciones tienen algunos requisitos.
El campo "arguments" Se toma una lista de cadenas, pero todas, excepto el último, son argumentos que se pasan al trabajador durante el inicio. El último elemento de la lista "argumentos" es un argumento
flag-file
(@-preceded). Los trabajadores leen los argumentos del archivo de marcas especificado por WorkRequest. Tu regla puede escribir argumentos que no sean de inicio para el trabajador en este archivo marcador.El campo "execution-requirements", que toma un diccionario que contiene
"supports-workers" : "1"
,"supports-multiplex-workers" : "1"
o ambosLos campos “arguments” y “execution-requirements” son obligatorios para todas las acciones que se envían a los trabajadores. Además, las acciones que deben ejecutar los trabajadores JSON deben incluir
"requires-worker-protocol" : "json"
en el campo de requisitos de ejecución."requires-worker-protocol" : "proto"
también es un requisito de ejecución válido, aunque no es necesario para los trabajadores proto, ya que son los predeterminados.También puedes establecer un
worker-key-mnemonic
en los requisitos de ejecución. Esto puede ser útil si vuelves a usar el ejecutable para varios tipos de acciones y deseas distinguir las acciones de este trabajador.Los archivos temporales generados en el curso de la acción deben guardarse en el directorio del trabajador. Esto habilita la zona de pruebas.
Si suponemos que hay una definición de regla con el atributo "worker" que se describió antes, además de un atributo "srcs" que representa las entradas, un atributo "output" que representa las salidas y un atributo "args" que representa los argumentos de inicio del trabajador, la llamada a ctx.actions.run
podría ser la siguiente:
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 ver otro ejemplo, consulta Implementa trabajadores persistentes.
Ejemplos
La base de código de Bazel usa trabajadores del compilador Java, además de un trabajador JSON de ejemplo que se usa en nuestras pruebas de integración.
Puedes usar scaffolding para convertir cualquier herramienta basada en Java en un trabajador pasando la devolución de llamada correcta.
Para ver un ejemplo de una regla que usa un trabajador, consulta la prueba de integración de trabajadores de Bazel.
Los colaboradores externos implementaron trabajadores en una variedad de lenguajes. Consulta las implementaciones de Polyglot para trabajadores persistentes de Bazel. Puedes encontrar muchos más ejemplos en GitHub.