Skip to content

Commit 6b04326

Browse files
committed
Offload gitoxide to a tokio blocking thread
1 parent dd6ae4f commit 6b04326

File tree

4 files changed

+64
-8
lines changed

4 files changed

+64
-8
lines changed

gitlab-runner/examples/demo-runner.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use gitlab_runner::{
1212
};
1313
use serde::Deserialize;
1414
use tokio::signal::unix::{signal, SignalKind};
15+
use tokio_util::sync::CancellationToken;
1516
use tracing::{debug, info};
1617
use tracing_subscriber::prelude::*;
1718
use url::Url;
@@ -141,13 +142,16 @@ impl Run {
141142
};
142143
let reference = p.next();
143144

145+
let cancel_token = CancellationToken::new();
144146
let repo_path = clone_git_repository(
145147
self.job.build_dir(),
146148
url,
147149
reference,
148150
std::iter::empty::<&str>(),
149151
Some(1),
152+
cancel_token,
150153
)
154+
.await
151155
.map_err(|e| outputln!("Failed to checkout repo: {}", e.to_string()))?;
152156

153157
std::fs::rename(repo_path, path)
@@ -160,9 +164,11 @@ impl Run {
160164
Some(path) => self.job.build_dir().join(path),
161165
_ => self.job.build_dir().to_path_buf(),
162166
};
167+
let cancel_token = CancellationToken::new();
163168
let temp_repo_path = self
164169
.job
165-
.clone_git_repository()
170+
.clone_git_repository(cancel_token)
171+
.await
166172
.map_err(|e| outputln!("Failed to checkout repo: {}", e.to_string()))?;
167173

168174
outputln!("Checked out to {:?}", temp_repo_path);

gitlab-runner/src/client.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,10 @@ pub enum GitCheckoutError {
309309
GitWorktreeCheckout(#[from] gix::worktree::state::checkout::Error),
310310
#[error(transparent)]
311311
GitHeadPeel(#[from] gix::head::peel::Error),
312+
#[error(transparent)]
313+
ThreadJoinError(#[from] tokio::task::JoinError),
314+
#[error("Checkout cancelled")]
315+
Cancelled,
312316
#[error("Job does not allow fetch")]
313317
FetchNotAllowed,
314318
#[error("Failed to find commit")]

gitlab-runner/src/job.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::path::{Path, PathBuf};
1010
use std::sync::{Arc, Mutex};
1111
use tokio::io::AsyncWrite;
1212
use tokio_retry2::strategy::{jitter, FibonacciBackoff};
13+
use tokio_util::sync::CancellationToken;
1314
use tracing::info;
1415

1516
use crate::client::Error as ClientError;
@@ -370,7 +371,10 @@ impl Job {
370371
///
371372
/// This creates a new path for the repo as gitoxide deletes it on failure.
372373
#[allow(clippy::result_large_err)]
373-
pub fn clone_git_repository(&self) -> Result<PathBuf, GitCheckoutError> {
374+
pub async fn clone_git_repository(
375+
&self,
376+
cancel_token: CancellationToken,
377+
) -> Result<PathBuf, GitCheckoutError> {
374378
if !self.response.allow_git_fetch {
375379
return Err(GitCheckoutError::FetchNotAllowed);
376380
}
@@ -381,6 +385,8 @@ impl Job {
381385
Some(&self.response.git_info.sha),
382386
&self.response.git_info.refspecs,
383387
Some(self.response.git_info.depth),
388+
cancel_token,
384389
)
390+
.await
385391
}
386392
}

gitlab-runner/src/lib.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ use std::fmt::Write;
3333
use std::num::NonZeroU32;
3434
use std::path::{Path, PathBuf};
3535
use std::sync::atomic::AtomicBool;
36+
use std::sync::atomic::Ordering;
37+
use std::sync::Arc;
3638
use tokio::time::{sleep, Duration};
3739
use tracing::warn;
3840
use url::Url;
@@ -460,27 +462,65 @@ impl Runner {
460462
}
461463
}
462464

463-
// TODO: Should clone_git_repository be async
464-
// gitoxide doesn't currently have async implemented for http backend
465+
// TODO: Should we re-export gix::progress to provide a means to monitor progress
465466
// TODO: Is "clippy::result_large_err" the best solution to GitCheckoutError
466467

468+
/// Fetch and checkout a given worktree on a thread
469+
///
470+
/// See: [clone_git_repository_sync]
471+
pub async fn clone_git_repository(
472+
parent_path: &Path,
473+
repo_url: &str,
474+
head_ref: Option<&str>,
475+
refspecs: impl IntoIterator<Item = impl AsRef<str>>,
476+
depth: Option<u32>,
477+
cancel_token: CancellationToken,
478+
) -> Result<PathBuf, GitCheckoutError> {
479+
let parent_path = parent_path.to_owned();
480+
let repo_url = repo_url.to_owned();
481+
let head_ref = head_ref.map(|a| a.to_owned());
482+
let should_interrupt: Arc<AtomicBool> = Default::default();
483+
let should_interrupt_cancel = should_interrupt.clone();
484+
let refspecs: Vec<_> = refspecs
485+
.into_iter()
486+
.map(|s| s.as_ref().to_owned())
487+
.collect();
488+
// offload the clone operation to one of the tokio runtimes blocking threads
489+
tokio::select! {
490+
result = tokio::task::spawn_blocking(move || {
491+
clone_git_repository_sync(
492+
&parent_path,
493+
&repo_url,
494+
head_ref.as_deref(),
495+
refspecs,
496+
depth,
497+
&should_interrupt,
498+
)
499+
}) => result?,
500+
_ = cancel_token.cancelled() => {
501+
should_interrupt_cancel.store(true, Ordering::SeqCst);
502+
Err(GitCheckoutError::Cancelled)
503+
}
504+
}
505+
}
506+
467507
/// Fetch and checkout a given worktree
468508
///
469509
/// This creates a new path for the repo as gitoxide deletes it on failure.
470510
#[allow(clippy::result_large_err)]
471-
pub fn clone_git_repository(
511+
pub fn clone_git_repository_sync(
472512
parent_path: &Path,
473513
repo_url: &str,
474514
head_ref: Option<&str>,
475515
refspecs: impl IntoIterator<Item = impl AsRef<str>>,
476516
depth: Option<u32>,
517+
should_interrupt: &AtomicBool,
477518
) -> Result<PathBuf, GitCheckoutError> {
478519
let repo_dir = tempfile::Builder::new()
479520
.prefix("repo_")
480521
.tempdir_in(parent_path)?;
481522

482523
// TODO: Should we expose the ability to interrupt / report progress
483-
let should_interrupt = AtomicBool::new(false);
484524
let mut progress = progress::Discard;
485525

486526
// TODO: Is Options::isolated correct here?
@@ -543,7 +583,7 @@ pub fn clone_git_repository(
543583
}
544584

545585
let checkout_progress = progress.add_child("checkout".to_string());
546-
let (checkout, _outcome) = fetch.fetch_then_checkout(checkout_progress, &should_interrupt)?;
586+
let (checkout, _outcome) = fetch.fetch_then_checkout(checkout_progress, should_interrupt)?;
547587

548588
let repo = checkout.persist();
549589

@@ -596,7 +636,7 @@ pub fn clone_git_repository(
596636
repo.objects.clone().into_arc()?,
597637
&files_progress,
598638
&bytes_progress,
599-
&should_interrupt,
639+
should_interrupt,
600640
Default::default(),
601641
)?;
602642

0 commit comments

Comments
 (0)