diff --git a/packages/server/src/target/build.rs b/packages/server/src/target/build.rs
index 64ae4dc73..4cf541d70 100644
--- a/packages/server/src/target/build.rs
+++ b/packages/server/src/target/build.rs
@@ -116,72 +116,127 @@ impl Server {
return Ok(Some(output));
}
- // Get a remote build if one exists that satisfies the retry constraint.
- 'a: {
- // Find a build.
- let futures = self
- .get_remote_clients()
- .await?
- .into_values()
- .map(|client| {
- let arg = arg.clone();
- Box::pin(async move {
- let arg = tg::target::build::Arg {
- create: false,
+ // Create a build id for the local build, in order to avoid borrow checking errors when canceling in the case that a remote returns first.
+ let build_id = tg::build::Id::new();
+
+ // Create futures.
+ let local = self.try_create_local_build(build_id.clone(), id.clone(), arg.clone());
+ let remote = self.try_get_remote_build(id.clone(), arg.clone());
+
+ // Race the local/remote builds.
+ let build = match future::select(std::pin::pin!(local), std::pin::pin!(remote)).await {
+ future::Either::Left((local, remote)) => {
+ if let Ok(Some(local)) = local {
+ Some(local)
+ } else {
+ remote.await?
+ }
+ },
+ future::Either::Right((remote, local)) => {
+ if let Ok(Some(build)) = remote {
+ // Cancel the local build in the case that the remote won the race.
+ let server = self.clone();
+ tokio::spawn(async move {
+ let arg = tg::build::finish::Arg {
+ status: tg::build::Status::Canceled,
+ error: None,
+ output: None,
remote: None,
- ..arg.clone()
};
- let tg::target::build::Output { build } =
- client.build_target(id, arg).await?;
- let build = tg::Build::with_id(build);
- Ok::<_, tg::Error>(Some((build, client)))
- })
- })
- .collect_vec();
+ server.try_finish_build(&build_id, arg).await.ok();
+ });
+ Some(build)
+ } else {
+ local.await?
+ }
+ },
+ };
- // Wait for the first build.
- if futures.is_empty() {
- break 'a;
- }
- let Ok((Some((build, _remote)), _)) = future::select_ok(futures).await else {
- break 'a;
- };
+ // Bail if no build was found/spawned.
+ let Some(build) = build else {
+ return Ok(None);
+ };
- // Add the build as a child of the parent.
- if let Some(parent) = arg.parent.as_ref() {
- self.try_add_build_child(parent, build.id()).await.map_err(
- |source| tg::error!(!source, %parent, %child = build.id(), "failed to add build as a child"),
- )?;
- }
+ // Add the build as a child of the parent.
+ if let Some(parent) = arg.parent.as_ref() {
+ self.try_add_build_child(parent, build.id()).await.map_err(
+ |source| tg::error!(!source, %parent, %child = build.id(), "failed to add build as a child"),
+ )?;
+ }
- // Touch the build.
- tokio::spawn({
- let server = self.clone();
- let build = build.clone();
- async move {
- let arg = tg::build::touch::Arg { remote: None };
- server.touch_build(build.id(), arg).await.ok();
- }
- });
+ // Create the output.
+ let output = tg::target::build::Output {
+ build: build.id().clone(),
+ };
+ Ok(Some(output))
+ }
- // Create the output.
- let output = tg::target::build::Output {
- build: build.id().clone(),
- };
+ async fn try_get_remote_build(
+ &self,
+ id: tg::target::Id,
+ arg: tg::target::build::Arg,
+ ) -> tg::Result