Skip to content

Commit 181913b

Browse files
KapJIfacebook-github-bot
authored andcommitted
Add remote persistent worker support (#800)
Summary: Pull Request resolved: #800 Differential Revision: D65214397
1 parent 6abf420 commit 181913b

File tree

40 files changed

+1196
-6
lines changed

40 files changed

+1196
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: build_example_persistent_worker
2+
inputs:
3+
buildbuddyApiKey:
4+
description: "The API key for BuildBuddy remote cache and execution."
5+
required: true
6+
runs:
7+
using: composite
8+
steps:
9+
- name: Build examples/persistent_worker directory
10+
env:
11+
BUILDBUDDY_API_KEY: ${{ inputs.buildbuddyApiKey }}
12+
run: |-
13+
cd examples/persistent_worker
14+
export PATH="$RUNNER_TEMP/artifacts:$PATH"
15+
./test.sh
16+
shell: bash

.github/workflows/build-and-test.yml

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: Build and test
22
on:
33
push:
44
pull_request:
5+
workflow_dispatch: # allows manual triggering
56
jobs:
67
linux-build-and-test:
78
runs-on: 4-core-ubuntu
@@ -69,6 +70,9 @@ jobs:
6970
$RUNNER_TEMP/artifacts/buck2 test //... -v 2
7071
- uses: ./.github/actions/build_example_conan
7172
- uses: ./.github/actions/build_example_no_prelude
73+
- uses: ./.github/actions/build_example_persistent_worker
74+
with:
75+
buildbuddyApiKey: ${{ secrets.BUILDBUDDY_API_KEY }}
7276
- uses: ./.github/actions/setup_reindeer
7377
- uses: ./.github/actions/build_bootstrap
7478
windows-build-examples:

app/buck2_action_impl/src/actions/impls/run.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use buck2_core::execution_types::executor_config::RemoteExecutorCustomImage;
4141
use buck2_core::execution_types::executor_config::RemoteExecutorDependency;
4242
use buck2_core::fs::buck_out_path::BuildArtifactPath;
4343
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
44+
use buck2_error::buck2_error;
4445
use buck2_error::starlark_error::from_starlark;
4546
use buck2_error::BuckErrorContext;
4647
use buck2_events::dispatch::span_async_simple;
@@ -263,6 +264,7 @@ struct UnpackedWorkerValues<'v> {
263264
exe: &'v dyn CommandLineArgLike,
264265
id: WorkerId,
265266
concurrency: Option<usize>,
267+
remote: bool,
266268
}
267269

268270
struct UnpackedRunActionValues<'v> {
@@ -319,6 +321,7 @@ impl RunAction {
319321
exe: worker.exe_command_line(),
320322
id: WorkerId(worker.id),
321323
concurrency: worker.concurrency(),
324+
remote: worker.remote(),
322325
});
323326

324327
Ok(UnpackedRunActionValues {
@@ -334,6 +337,7 @@ impl RunAction {
334337
&self,
335338
action_execution_ctx: &dyn ActionExecutionCtx,
336339
artifact_visitor: &mut impl CommandLineArtifactVisitor,
340+
actx: &dyn ActionExecutionCtx,
337341
) -> buck2_error::Result<(ExpandedCommandLine, Option<WorkerSpec>)> {
338342
let fs = &action_execution_ctx.executor_fs();
339343
let mut cli_ctx = DefaultCommandLineContext::new(fs);
@@ -351,10 +355,31 @@ impl RunAction {
351355
.exe
352356
.add_to_command_line(&mut worker_rendered, &mut cli_ctx)?;
353357
worker.exe.visit_artifacts(artifact_visitor)?;
358+
let worker_key = if worker.remote {
359+
let mut worker_visitor = SimpleCommandLineArtifactVisitor::new();
360+
worker.exe.visit_artifacts(&mut worker_visitor)?;
361+
if !worker_visitor.outputs.is_empty() {
362+
// TODO[AH] create appropriate error enum value.
363+
return Err(buck2_error!(
364+
[],
365+
"remote persistent worker command should not produce an output"
366+
));
367+
}
368+
let worker_inputs: Vec<&ArtifactGroupValues> = worker_visitor
369+
.inputs()
370+
.map(|group| actx.artifact_values(group))
371+
.collect();
372+
let (_, worker_digest) =
373+
metadata_content(fs.fs(), &worker_inputs, actx.digest_config())?;
374+
Some(worker_digest)
375+
} else {
376+
None
377+
};
354378
Some(WorkerSpec {
355379
exe: worker_rendered,
356380
id: worker.id,
357381
concurrency: worker.concurrency,
382+
remote_key: worker_key,
358383
})
359384
} else {
360385
None
@@ -426,7 +451,7 @@ impl RunAction {
426451
let executor_fs = ctx.executor_fs();
427452
let fs = executor_fs.fs();
428453

429-
let (expanded, worker) = self.expand_command_line_and_worker(ctx, visitor)?;
454+
let (expanded, worker) = self.expand_command_line_and_worker(ctx, visitor, ctx)?;
430455

431456
// TODO (@torozco): At this point, might as well just receive the list already. Finding
432457
// those things in a HashMap is just not very useful.

app/buck2_build_api/src/actions/execute/action_executor.rs

+1
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,7 @@ mod tests {
773773
CommandGenerationOptions {
774774
path_separator: PathSeparatorKind::Unix,
775775
output_paths_behavior: Default::default(),
776+
use_remote_persistent_workers: false,
776777
},
777778
Default::default(),
778779
),

app/buck2_build_api/src/interpreter/rule_defs/command_executor_config.rs

+3
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub fn register_command_executor_config(builder: &mut GlobalsBuilder) {
8585
/// * `allow_hybrid_fallbacks_on_failure`: Whether to allow fallbacks when the result is failure (i.e. the command failed on the primary, but the infra worked)
8686
/// * `use_windows_path_separators`: Whether to use Windows path separators in command line arguments
8787
/// * `use_persistent workers`: Whether to use persistent workers for local execution if they are available
88+
/// * `use_remote_persistent_workers`: Whether to use persistent workers for remote execution if they are available
8889
/// * `allow_cache_uploads`: Whether to upload local actions to the RE cache
8990
/// * `max_cache_upload_mebibytes`: Maximum size to upload in cache uploads
9091
/// * `experimental_low_pass_filter`: Whether to use the experimental low pass filter
@@ -110,6 +111,7 @@ pub fn register_command_executor_config(builder: &mut GlobalsBuilder) {
110111
#[starlark(default = false, require = named)] allow_hybrid_fallbacks_on_failure: bool,
111112
#[starlark(default = false, require = named)] use_windows_path_separators: bool,
112113
#[starlark(default = false, require = named)] use_persistent_workers: bool,
114+
#[starlark(default = false, require = named)] use_remote_persistent_workers: bool,
113115
#[starlark(default = false, require = named)] allow_cache_uploads: bool,
114116
#[starlark(default = NoneOr::None, require = named)] max_cache_upload_mebibytes: NoneOr<
115117
i32,
@@ -322,6 +324,7 @@ pub fn register_command_executor_config(builder: &mut GlobalsBuilder) {
322324
PathSeparatorKind::Unix
323325
},
324326
output_paths_behavior,
327+
use_remote_persistent_workers,
325328
},
326329
}
327330
};

app/buck2_build_api/src/interpreter/rule_defs/provider/builtin/worker_info.rs

+11
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub struct WorkerInfoGen<V: ValueLifetimeless> {
4747
pub exe: ValueOfUncheckedGeneric<V, FrozenStarlarkCmdArgs>,
4848
// Maximum number of concurrent commands to execute on a worker instance without queuing
4949
pub concurrency: ValueOfUncheckedGeneric<V, NoneOr<usize>>,
50+
// Remote execution capable worker
51+
pub remote: ValueOfUncheckedGeneric<V, bool>,
5052

5153
pub id: u64,
5254
}
@@ -64,6 +66,7 @@ fn worker_info_creator(globals: &mut GlobalsBuilder) {
6466
#[starlark(require = named, default = NoneOr::None)] concurrency: NoneOr<
6567
ValueOf<'v, usize>,
6668
>,
69+
#[starlark(require = named, default = false)] remote: bool,
6770
eval: &mut Evaluator<'v, '_, '_>,
6871
) -> starlark::Result<WorkerInfo<'v>> {
6972
let heap = eval.heap();
@@ -74,6 +77,7 @@ fn worker_info_creator(globals: &mut GlobalsBuilder) {
7477
exe,
7578
id,
7679
concurrency: heap.alloc_typed_unchecked(concurrency).cast(),
80+
remote: heap.alloc_typed_unchecked(remote).cast(),
7781
})
7882
}
7983
}
@@ -92,6 +96,13 @@ impl<'v, V: ValueLike<'v>> WorkerInfoGen<V> {
9296
.expect("validated at construction")
9397
.into_option()
9498
}
99+
100+
pub fn remote(&self) -> bool {
101+
self.remote
102+
.to_value()
103+
.unpack()
104+
.expect("validated at construction")
105+
}
95106
}
96107

97108
fn validate_worker_info<'v, V>(info: &WorkerInfoGen<V>) -> buck2_error::Result<()>

app/buck2_build_api_tests/src/interpreter/rule_defs/provider/builtin/worker_info.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fn run_display() {
2424
.run_starlark_bzl_test(
2525
r#"
2626
def test():
27-
assert_eq('WorkerInfo(exe=cmd_args("x"), concurrency=None)', str(WorkerInfo(exe="x")))
27+
assert_eq('WorkerInfo(exe=cmd_args("x"), concurrency=None, remote=False)', str(WorkerInfo(exe="x")))
2828
"#,
2929
)
3030
.unwrap();

app/buck2_core/src/execution_types/executor_config.rs

+2
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ impl Default for CacheUploadBehavior {
282282
pub struct CommandGenerationOptions {
283283
pub path_separator: PathSeparatorKind,
284284
pub output_paths_behavior: OutputPathsBehavior,
285+
pub use_remote_persistent_workers: bool,
285286
}
286287

287288
#[derive(Debug, Eq, PartialEq, Hash, Allocative, Clone)]
@@ -313,6 +314,7 @@ impl CommandExecutorConfig {
313314
options: CommandGenerationOptions {
314315
path_separator: PathSeparatorKind::system_default(),
315316
output_paths_behavior: Default::default(),
317+
use_remote_persistent_workers: false,
316318
},
317319
})
318320
}

app/buck2_execute/src/execute/command_executor.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use buck2_core::fs::artifact_path_resolver::ArtifactFs;
2020
use buck2_core::fs::project_rel_path::ProjectRelativePath;
2121
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
2222
use buck2_directory::directory::fingerprinted_directory::FingerprintedDirectory;
23-
#[cfg(fbcode_build)]
2423
use buck2_error::buck2_error;
2524
use buck2_error::BuckErrorContext;
2625
use buck2_futures::cancellation::CancellationContext;
@@ -187,15 +186,45 @@ impl CommandExecutor {
187186
}
188187
CommandExecutionInput::ScratchPath(_) => None,
189188
});
189+
let mut platform = self.0.re_platform.clone();
190+
let args = if self.0.options.use_remote_persistent_workers
191+
&& let Some(worker) = request.worker()
192+
&& let Some(key) = worker.remote_key.as_ref()
193+
{
194+
platform.properties.push(RE::Property {
195+
name: "persistentWorkerKey".to_owned(),
196+
value: key.to_string(),
197+
});
198+
// TODO[AH] Ideally, Buck2 could generate an argfile on the fly.
199+
for arg in request.args() {
200+
if !(arg.starts_with("@")
201+
|| arg.starts_with("-flagfile")
202+
|| arg.starts_with("--flagfile"))
203+
{
204+
return Err(buck2_error!(
205+
[],
206+
"Remote persistent worker arguments must be passed as `@argfile`, `-flagfile=argfile`, or `--flagfile=argfile`."
207+
));
208+
}
209+
}
210+
worker
211+
.exe
212+
.iter()
213+
.chain(request.args().iter())
214+
.cloned()
215+
.collect()
216+
} else {
217+
request.all_args_vec()
218+
};
190219
let action = re_create_action(
191-
request.all_args_vec(),
220+
args,
192221
request.paths().output_paths(),
193222
request.working_directory(),
194223
request.env(),
195224
input_digest,
196225
action_metadata_blobs,
197226
request.timeout(),
198-
self.0.re_platform.clone(),
227+
platform,
199228
false,
200229
digest_config,
201230
self.0.options.output_paths_behavior,

app/buck2_execute/src/execute/request.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,12 @@ impl CommandExecutionPaths {
278278
#[derive(Copy, Clone, Dupe, Debug, Display, Allocative, Hash, PartialEq, Eq)]
279279
pub struct WorkerId(pub u64);
280280

281-
#[derive(Clone)]
281+
#[derive(Clone, Debug)]
282282
pub struct WorkerSpec {
283283
pub id: WorkerId,
284284
pub exe: Vec<String>,
285285
pub concurrency: Option<usize>,
286+
pub remote_key: Option<TrackedFileDigest>,
286287
}
287288

288289
/// The data contains the information about the command to be executed.

app/buck2_execute/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#![feature(try_trait_v2)]
1515
#![feature(used_with_arg)]
1616
#![feature(trait_upcasting)]
17+
#![feature(let_chains)]
1718

1819
pub mod artifact;
1920
pub mod artifact_utils;

app/buck2_server/src/daemon/common.rs

+1
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ pub fn get_default_executor_config(host_platform: HostPlatformOverride) -> Comma
500500
options: CommandGenerationOptions {
501501
path_separator: get_default_path_separator(host_platform),
502502
output_paths_behavior: Default::default(),
503+
use_remote_persistent_workers: false,
503504
},
504505
}
505506
}

app/buck2_test/src/orchestrator.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,7 @@ impl<'b> BuckTestOrchestrator<'b> {
11801180
options: CommandGenerationOptions {
11811181
path_separator: PathSeparatorKind::system_default(),
11821182
output_paths_behavior: Default::default(),
1183+
use_remote_persistent_workers: false,
11831184
},
11841185
};
11851186
let CommandExecutorResponse {
@@ -1714,6 +1715,7 @@ impl<'a> Execute2RequestExpander<'a> {
17141715
exe: worker_rendered,
17151716
id: WorkerId(worker.id),
17161717
concurrency: worker.concurrency(),
1718+
remote_key: None,
17171719
})
17181720
}
17191721
_ => None,
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[cells]
2+
root = .
3+
prelude = prelude
4+
toolchains = toolchains
5+
none = none
6+
7+
[cell_aliases]
8+
config = prelude
9+
fbcode = none
10+
fbsource = none
11+
buck = none
12+
13+
[external_cells]
14+
prelude = bundled
15+
16+
[parser]
17+
target_platform_detector_spec = target:root//...->prelude//platforms:default
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[buck2]
2+
digest_algorithms = SHA256
3+
4+
[buck2_re_client]
5+
engine_address = grpc://remote.buildbuddy.io
6+
action_cache_address = grpc://remote.buildbuddy.io
7+
cas_address = grpc://remote.buildbuddy.io
8+
tls = true
9+
http_headers = \
10+
x-buildbuddy-api-key:$BUILDBUDDY_API_KEY
11+
12+
[build]
13+
execution_platforms = root//platforms:buildbuddy
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[buck2]
2+
digest_algorithms = SHA256
3+
4+
[buck2_re_client]
5+
engine_address = grpc://remote.buildbuddy.io
6+
action_cache_address = grpc://remote.buildbuddy.io
7+
cas_address = grpc://remote.buildbuddy.io
8+
tls = true
9+
http_headers = \
10+
x-buildbuddy-api-key:$BUILDBUDDY_API_KEY
11+
12+
[build]
13+
execution_platforms = root//platforms:buildbuddy-persistent-workers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
execution_platforms = root//platforms:local-persistent-workers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
execution_platforms = root//platforms:local

examples/persistent_worker/.buckroot

Whitespace-only changes.

examples/persistent_worker/.envrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# specify the following:
2+
# - BUILDBUDDY_API_KEY
3+
source_env_if_exists .envrc.private

examples/persistent_worker/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.buckconfig.local
2+
.direnv
3+
.envrc.private
4+
prelude

examples/persistent_worker/BUCK

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
load("defs.bzl", "demo", "worker")
2+
3+
python_binary(
4+
name = "one_shot",
5+
main = "one_shot.py",
6+
)
7+
8+
python_binary(
9+
name = "worker_py",
10+
main = "persistent_worker.py",
11+
deps = [
12+
"//proto/bazel:worker_protocol_pb2",
13+
"//proto/buck2:worker_pb2",
14+
],
15+
)
16+
17+
worker(
18+
name = "worker",
19+
visibility = ["PUBLIC"],
20+
worker = ":worker_py",
21+
)
22+
23+
[
24+
demo(name = "demo-" + str(i))
25+
for i in range(4)
26+
]

0 commit comments

Comments
 (0)