Skip to content

Persistent Workers

Persistent workers keep compatible compiler and tool processes alive across remote actions. This reduces repeated tool startup cost for JVM-heavy tools such as javac, scalac, and kotlinc, and for worker wrappers around tools such as tsc.

The repository examples live in deployment-examples/persistent-workers.

NativeLink uses Bazel execution requirements to choose the persistent worker path:

execution_requirements = {
"supports-workers": "1",
"requires-worker-protocol": "proto", # or "json"
}

If requires-worker-protocol is omitted, NativeLink treats the action as proto.

def _javac_worker_impl(ctx):
args = ctx.actions.args()
args.add("@%s" % ctx.outputs.argfile.path)
ctx.actions.run(
executable = ctx.executable.javac_worker,
arguments = [args],
inputs = ctx.files.srcs + [ctx.outputs.argfile],
outputs = [ctx.outputs.jar],
mnemonic = "Javac",
execution_requirements = {
"supports-workers": "1",
"requires-worker-protocol": "proto",
},
)

NativeLink derives a WorkerKey from:

  • the executable path
  • startup arguments before the first @argfile
  • requires-worker-protocol

Actions with the same key can reuse the same warm process. NativeLink starts the tool with --persistent_worker, sends one WorkRequest per action, and reads one WorkResponse per action.

The implementation includes an integration test that sends two remote actions to one JSON worker. The first action writes 1, and the second writes 2, proving that the second action reused the already-running worker process.

Run the focused test gates with:

Terminal window
bazel test \
//nativelink-worker:unit_test \
//nativelink-worker:integration \
//nativelink-scheduler:integration \
--test_output=errors