Persistente Worker erstellen

Persistente Worker können Ihren Build beschleunigen. Wenn Sie in Ihrem Build wiederkehrende Aktionen mit hohen Startkosten haben oder vom aktionsübergreifenden Caching profitieren würden, möchten Sie vielleicht Ihren eigenen nichtflüchtigen Worker implementieren, um diese Aktionen auszuführen.

Der Bazel-Server kommuniziert mit dem Worker über stdin/stdout. Er unterstützt Protokollzwischenspeicher oder JSON-Strings.

Die Worker-Implementierung besteht aus zwei Teilen:

Arbeiter machen

Ein nichtflüchtiger Worker erfüllt einige Anforderungen:

  • Sie liest WorkRequests aus stdin.
  • Es schreibt WorkResponses (und nur WorkResponses) in sein stdout.
  • Er akzeptiert das Flag --persistent_worker. Der Wrapper muss das Befehlszeilen-Flag --persistent_worker erkennen und nur dauerhaft sein, wenn dieses Flag übergeben wird. Andernfalls muss eine One-Shot-Kompilierung und ein One-Shot-Vorgang ausgeführt werden.

Wenn Ihr Programm diese Anforderungen erfüllt, kann es als persistenter Worker verwendet werden.

Geschäftliche Anfragen

WorkRequest enthält eine Liste von Argumenten für den Worker, eine Liste von Pfad-Digest-Paaren, die die Eingaben darstellt, auf die der Worker zugreifen kann. Diese wird zwar nicht erzwungen, Sie können diese Informationen aber für das Caching verwenden, und eine Anfrage-ID, die für Singleplex-Worker gleich 0 ist.

HINWEIS: Während die Protokollpufferspezifikation "snake case" (request_id) verwendet, verwendet das JSON-Protokoll &camel case" (requestId). Dieses Dokument verwendet die Camel-Case-Schreibweise in den JSON-Beispielen, aber snake-Fall, wenn es unabhängig vom Protokoll um das Feld geht.

{
  "arguments" : ["--some_argument"],
  "inputs" : [
    { "path": "/path/to/my/file/1", "digest": "fdk3e2ml23d"},
    { "path": "/path/to/my/file/2", "digest": "1fwqd4qdd" }
 ],
  "requestId" : 12
}

Mit dem optionalen Feld verbosity kann eine zusätzliche Debugging-Ausgabe vom Worker angefordert werden. Der Worker entscheidet selbst, was und wie er ausgegeben wird. Je höher der Wert, desto ausführlicher die Ausgabe. Wenn Sie das Flag --worker_verbose an Bazel übergeben, wird das Feld verbosity auf 10 gesetzt. Kleinere oder größere Werte können jedoch manuell für unterschiedliche Ausgabemengen verwendet werden.

Das optionale Feld sandbox_dir wird nur von Workern verwendet, die Multiplex-Sandboxing unterstützen.

Geschäftliche Antworten

Ein WorkResponse enthält eine Anfrage-ID, einen Exit-Code ungleich null oder einen Ausgabestring, der alle Fehler beschreibt, die bei der Verarbeitung oder Ausführung der Anfrage aufgetreten sind. Das Feld output enthält eine kurze Beschreibung. Vollständige Logs können in den stderr des Workers geschrieben werden. Da Worker nur WorkResponses in stdout schreiben dürfen, leitet der Worker die stdout aller verwendeten Tools häufig an stderr weiter.

{
  "exitCode" : 1,
  "output" : "Action failed with the following message:\nCould not find input
    file \"/path/to/my/file/1\"",
  "requestId" : 12
}

Gemäß der Norm für Protobufs sind alle Felder optional. Bazel erfordert jedoch, dass WorkRequest und die entsprechende WorkResponse dieselbe Anfrage-ID haben. Daher muss die Anfrage-ID angegeben werden, wenn sie ungleich null ist. Dies ist ein gültiger WorkResponse.

{
  "requestId" : 12,
}

Eine request_id von 0 gibt eine „Singleplex“-Anfrage an, die verwendet wird, wenn diese Anfrage nicht parallel zu anderen Anfragen verarbeitet werden kann. Der Server garantiert, dass ein bestimmter Worker Anfragen erhält, bei denen entweder request_id 0 oder nur request_id größer als null ist. Singleplex-Anfragen werden nacheinander gesendet, z. B. wenn der Server keine weitere Anfrage sendet, bis er eine Antwort erhalten hat (Ausnahme: Abbruchanfragen, siehe unten).

Hinweise

  • Jedem Protokollzwischenspeicher wird die Länge im Format varint vorangestellt (siehe MessageLite.writeDelimitedTo()).
  • JSON-Anfragen und -Antworten ist kein Größenindikator vorangestellt.
  • JSON-Anfragen behalten die gleiche Struktur wie SavedModel bei. Verwenden Sie jedoch standardmäßig JSON und verwenden Sie die Camel-Case-Schreibweise für alle Feldnamen.
  • JSON-Worker müssen in diesen Nachrichten unbekannte Felder tolerieren und die fehlenden Protobuf-Standardwerte für fehlende Werte verwenden, um die gleichen abwärts- und vorwärtskompatiblen Kompatibilitätsattribute wie für Protokollpuffer beizubehalten.
  • Bazel speichert Anfragen als Protobufs und konvertiert sie in JSON mit dem JSON-Format Protobuf.

Stornierung

Mitarbeiter können optional das Abbrechen von Arbeitsanfragen vor ihrem Abschluss zulassen. Dies ist besonders nützlich in Verbindung mit der dynamischen Ausführung, bei der die lokale Ausführung regelmäßig durch eine schnellere Remote-Ausführung unterbrochen werden kann. Fügen Sie dazu supports-worker-cancellation: 1 in das Feld execution-requirements ein (siehe unten) und legen Sie das Flag --experimental_worker_cancellation fest.

Eine Anfrage zum Abbrechen ist eine WorkRequest, für die das Feld cancel festgelegt ist. Ähnlich verhält es sich bei einer Antwort zur Stornierung: eine WorkResponse mit dem Feld was_cancelled. Das einzige andere Feld, das in einer Abbruch- oder Stornierungsantwort enthalten sein muss, ist request_id. Damit wird angegeben, welche Anfrage abgebrochen wird. Das Feld request_id ist für Singleplex-Worker gleich 0 oder für Multiplex-Worker den Wert request_id, der zuvor an WorkRequest gesendet wurde. Der Server kann Stornierungsanfragen für Anfragen senden, auf die der Worker bereits geantwortet hat. In diesem Fall muss die Stornierungsanfrage ignoriert werden.

Jede WorkRequest-Nachricht ohne Stornierung muss genau einmal beantwortet werden, unabhängig davon, ob sie abgebrochen wurde oder nicht. Sobald der Server eine Anfrage zum Abbrechen gesendet hat, kann der Worker mit einer WorkResponse antworten, in der request_id und das Feld was_cancelled auf „true“ gesetzt sind. Das Senden einer normalen WorkResponse wird ebenfalls akzeptiert, aber die Felder output und exit_code werden ignoriert.

Sobald eine Antwort für WorkRequest gesendet wurde, darf der Worker die Dateien in seinem Arbeitsverzeichnis nicht mehr berühren. Der Server kann Dateien, einschließlich temporärer Dateien, bereinigen.

Regel erstellen, die den Worker verwendet

Außerdem müssen Sie eine Regel erstellen, durch die Aktionen generiert werden, die vom Worker ausgeführt werden sollen. Das Erstellen einer Starlark-Regel, die einen Worker verwendet, entspricht dem Erstellen einer anderen Regel.

Außerdem muss die Regel einen Verweis auf den Worker selbst enthalten und es gelten bestimmte Anforderungen für die erzeugten Aktionen.

Auf den Worker verweisen

Die Regel, die den Worker verwendet, muss ein Feld enthalten, das auf den Worker selbst verweist. Sie müssen daher eine Instanz einer \*\_binary-Regel erstellen, um den Worker zu definieren. Wenn Ihr Worker MyWorker.Java heißt, könnte dies die zugehörige Regel sein:

java_binary(
    name = "worker",
    srcs = ["MyWorker.Java"],
)

Dadurch wird das Label „Worker“ erstellt, das sich auf die Worker-Binärdatei bezieht. Anschließend definieren Sie eine Regel, die den Worker nutzt. Diese Regel sollte ein Attribut definieren, das sich auf die Worker-Binärdatei bezieht.

Wenn sich die von Ihnen erstellte Worker-Binärdatei in einem Paket namens „Arbeit“ befindet, das sich auf der obersten Ebene des Builds befindet, könnte dies die Attributdefinition sein:

"worker": attr.label(
    default = Label("//work:worker"),
    executable = True,
    cfg = "exec",
)

cfg = "exec" gibt an, dass der Worker zur Ausführung auf der Ausführungsplattform und nicht auf der Zielplattform erstellt werden soll (d.h. der Worker wird während des Builds als Tool verwendet).

Anforderungen an geschäftliche Aktionen

Die Regel, die den Worker verwendet, erstellt Aktionen, die der Worker ausführen soll. Für diese Aktionen gelten einige Voraussetzungen.

  • Das Feld "arguments" Dadurch wird eine Liste von Strings mit Ausnahme der letzten Argumente, die beim Start an den Worker übergeben werden, angenommen. Das letzte Element in der Liste „&arguments“ ist ein Argument flag-file (@-preceded). Worker lesen die Argumente aus der angegebenen Flag-Datei pro Job aus. Ihre Regel kann Argumente ohne Start für den Worker in diese Flag-Datei schreiben.

  • Das Feld " execution-requirements", das ein Wörterbuch mit "supports-workers" : "1", "supports-multiplex-workers" : "1" oder beidem enthält.

    Die Felder „Argumente“ und „Ausführungsanforderungen“ sind für alle Aktionen erforderlich, die an Worker gesendet werden. Außerdem müssen Aktionen, die von JSON-Workern ausgeführt werden sollen, "requires-worker-protocol" : "json" in das Feld für die Ausführungsanforderungen einfügen. "requires-worker-protocol" : "proto" ist auch eine gültige Ausführungsanforderung, die jedoch für Proto-Worker nicht erforderlich ist, da es sich hierbei um die Standardausführung handelt.

    Sie können in den Ausführungsanforderungen auch ein worker-key-mnemonic festlegen. Das kann nützlich sein, wenn Sie die ausführbare Datei für mehrere Aktionstypen wiederverwenden und Aktionen dieses Workers unterscheiden möchten.

  • Temporäre Dateien, die im Rahmen der Aktion generiert werden, sollten im Verzeichnis des Workers gespeichert werden. Dadurch wird das Sandboxing aktiviert.

Angenommen, eine Regeldefinition mit dem oben beschriebenen Attribut "Workers" stellt Folgendes dar, wobei zusätzlich das Attribut &srcs" für die Eingaben, ein Attribut "output" und die Werte für die Worker-Startargumente verwendet werden, könnte der Aufruf an ctx.actions.run aussehen:

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"]
 )

Ein weiteres Beispiel finden Sie unter Nichtflüchtige Worker implementieren.

Beispiele

Die Bazel-Codebasis verwendet zusätzlich zu einem Beispiel-JSON-Worker, der in unseren Integrationstests verwendet wird, Java-Compiler-Worker.

Sie können mit ihrem Gerüst ein beliebiges Java-basiertes Tool in einen Worker umwandeln, indem Sie den richtigen Callback übergeben.

Ein Beispiel für eine Regel, die einen Worker verwendet, finden Sie im Worker-Integrationstest von Bazel.

Externe Mitwirkende haben Worker in verschiedenen Sprachen implementiert. Weitere Informationen finden Sie unter Polyglot-Implementierungen von nichtflüchtigen Bazel-Workern. Auf GitHub finden Sie weitere Beispiele.