Los trabajadores persistentes pueden acelerar tu compilación. Si tienes acciones repetidas en tu compilación que tienen un costo de inicio alto o se beneficiarían del almacenamiento en caché entre acciones, es posible que desees implementar tu propio trabajador persistente para realizar estas acciones.
El servidor de Bazel se comunica con el trabajador a través de stdin
/stdout
y admite el uso de búferes de protocolo o cadenas JSON.
La implementación del trabajador tiene dos partes:
- El trabajador.
- Es la regla que usa el trabajador.
Cómo hacer el trabajador
Un trabajador persistente cumple con algunos requisitos:
- Lee WorkRequests desde su
stdin
. - Escribe WorkResponses (y solo
WorkResponse
s) en sustdout
. - Acepta la marca
--persistent_worker
. El wrapper debe reconocer la marca de línea de comandos--persistent_worker
y solo persistir si se pasa esa marca. De lo contrario, debe realizar una compilación única y salir.
Si tu programa cumple con estos requisitos, se puede usar como un proceso de trabajo persistente.
Solicitudes de trabajo
Un WorkRequest
contiene una lista de argumentos para el trabajador, una lista de pares de ruta de acceso y resumen 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 los trabajadores de un solo plex.
NOTA: Si bien la especificación de búfer de protocolo usa "snake case" (request_id
), el protocolo JSON usa "camel case" (requestId
). En este documento, se usa camel case en los ejemplos de JSON, pero snake case cuando se habla del campo, independientemente del protocolo.
{
"arguments" : ["--some_argument"],
"inputs" : [
{ "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
{ "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
],
"requestId" : 12
}
El campo opcional verbosity
se puede usar para solicitar información de depuración adicional del trabajador. Depende completamente del trabajador qué y cómo generar la salida. Los valores más altos indican una salida más detallada. Si pasas la marca --worker_verbose
a Bazel, se establece el campo verbosity
en 10, pero se pueden usar valores más pequeños o más grandes de forma manual para diferentes cantidades de salida.
El campo opcional sandbox_dir
solo lo usan los trabajadores que admiten el aislamiento múltiplex.
Respuestas de trabajo
Un objeto WorkResponse
contiene un ID de solicitud, un código de salida cero o distinto de cero, y un mensaje de salida que describe los errores que se encontraron durante el procesamiento o la ejecución de la solicitud. Un worker debe capturar los stdout
y stderr
de cualquier herramienta que llame y los debe informar a través de WorkResponse
. Escribir en el stdout
del proceso de trabajador no es seguro, ya que interferirá con el protocolo del trabajador.
Escribirlo en el stderr
del proceso de trabajo es seguro, pero el resultado se recopila en un archivo de registro por trabajador en lugar de atribuirse a acciones individuales.
{
"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 para los búferes de protocolo, todos los campos son opcionales. Sin embargo, Bazel requiere que WorkRequest
y el WorkResponse
correspondiente tengan el mismo ID de solicitud, por lo que se debe especificar el ID de solicitud si es distinto de cero. Este es un WorkResponse
válido.
{
"requestId" : 12,
}
Un valor de request_id
de 0 indica una solicitud "simple", que se usa cuando esta solicitud no se puede procesar en paralelo con otras solicitudes. El servidor garantiza que un trabajador determinado recibe solicitudes solo con request_id
0 o solo con 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 las solicitudes de cancelación, consulta a continuación).
Notas
- Cada búfer de protocolo está precedido por su longitud en formato
varint
(consultaMessageLite.writeDelimitedTo()
). - Las solicitudes y respuestas JSON no están precedidas por un indicador de tamaño.
- Las solicitudes JSON mantienen la misma estructura que protobuf, pero usan JSON estándar y minúsculas intermedias para todos los nombres de los campos.
- Para mantener las mismas propiedades de compatibilidad hacia atrás y hacia adelante que protobuf, los trabajadores de JSON deben tolerar los campos desconocidos en estos mensajes y usar los valores predeterminados de protobuf para los valores faltantes.
- Bazel almacena las solicitudes como archivos .proto y los convierte a JSON con el formato JSON de protobuf.
Cancelación
Los trabajadores pueden permitir, de forma opcional, que se cancelen las solicitudes de trabajo antes de que finalicen.
Esto es particularmente útil en relación con la ejecución dinámica, en la que la ejecución local puede interrumpirse con regularidad por 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
establecido (y, de manera similar, una respuesta de cancelación es un WorkResponse
con el campo was_cancelled
establecido). El único otro campo que debe estar en una solicitud o respuesta de cancelación es request_id
, que indica qué solicitud se debe cancelar. El campo request_id
será 0 para los trabajadores de un solo canal o el request_id
distinto de 0 de un WorkRequest
enviado anteriormente para los trabajadores de varios canales. El servidor puede enviar solicitudes de cancelación para las solicitudes a las que el trabajador ya respondió, en cuyo caso se debe ignorar la solicitud de cancelación.
Cada mensaje WorkRequest
que no sea de cancelación debe responderse exactamente una vez, independientemente de si se canceló o no. Una vez que el servidor haya enviado una solicitud de cancelación, el trabajador puede responder con un WorkResponse
con el request_id
establecido y el campo was_cancelled
establecido como verdadero. También se acepta enviar un WorkResponse
normal, pero se ignorarán los campos output
y exit_code
.
Una vez que se envía 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.
Cómo crear la regla que usa el trabajador
También deberás crear una regla que genere acciones para que las realice 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 hay 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 "worker", 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 archivo binario del trabajador.
Si el objeto binario del trabajador que compilaste se encuentra en un paquete llamado "work", que está 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 se debe compilar 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 de las acciones laborales
La regla que usa el trabajador crea acciones para que este las realice. Estas acciones tienen algunos requisitos.
El campo "arguments" Toma una lista de cadenas, todas excepto la última son argumentos que se pasan al trabajador al inicio. El último elemento de la lista "arguments" es un argumento
flag-file
(precedido por @). Los trabajadores leen los argumentos del archivo de marcas especificado para cada WorkRequest. Tu regla puede escribir argumentos que no son de inicio para el trabajador en este archivo de marcas.El campo "execution-requirements", que toma un diccionario que contiene
"supports-workers" : "1"
,"supports-multiplex-workers" : "1"
o ambos.Los 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 de 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 de 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 reutilizas el ejecutable para varios tipos de acciones y deseas distinguir las acciones por este trabajador.Los archivos temporales que se generen durante la acción se deben guardar en el directorio del trabajador. Esto habilita el aislamiento de pruebas.
Suponiendo una definición de regla con el atributo "worker" descrito anteriormente, 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 Implementación de trabajadores persistentes.
Ejemplos
La base de código de Bazel usa procesos de trabajo del compilador de Java, además de un proceso de trabajo de ejemplo en JSON que se usa en nuestras pruebas de integración.
Puedes usar su estructura 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 políglotas de los trabajadores persistentes de Bazel. Encontrarás muchos más ejemplos en GitHub.