use bytes::Bytes;
use std::future::Future;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::time::{interval_at, Duration, Instant, Interval, MissedTickBehavior};
use tracing::instrument::WithSubscriber;
use tracing::warn;
use tracing::Instrument;
use crate::client::{ArtifactWhen, Client, JobResponse, JobState};
use crate::job::{Job, JobData};
use crate::runlist::{RunList, RunListEntry};
use crate::uploader::Uploader;
use crate::{JobHandler, JobResult, Phase};
async fn run<F, J, Ret>(
job: Job,
client: Client,
response: Arc<JobResponse>,
process: F,
build_dir: PathBuf,
) -> JobResult
where
F: FnOnce(Job) -> Ret,
J: JobHandler,
Ret: Future<Output = Result<J, ()>>,
{
if let Err(e) = tokio::fs::create_dir(&build_dir).await {
job.trace(format!("Failed to remove build dir: {}", e));
return Err(());
}
let mut handler = process(job).await?;
let script = response.step(Phase::Script).ok_or(())?;
let script_result = handler.step(&script.script, Phase::Script).await;
if let Some(after) = response.step(Phase::AfterScript) {
let _ = handler.step(&after.script, Phase::AfterScript).await;
}
let upload = response.artifacts.get(0).map_or(false, |a| match a.when {
ArtifactWhen::Always => true,
ArtifactWhen::OnSuccess => script_result.is_ok(),
ArtifactWhen::OnFailure => script_result.is_err(),
});
let r = if upload {
if let Ok(mut uploader) = Uploader::new(client, &build_dir, response) {
let r = handler.upload_artifacts(&mut uploader).await;
if r.is_ok() {
uploader.upload().await.and(script_result)
} else {
r
}
} else {
warn!("Failed to create uploader");
Err(())
}
} else {
script_result
};
handler.cleanup().await;
if let Err(e) = tokio::fs::remove_dir_all(build_dir).await {
warn!("Failed to remove build dir: {}", e);
}
r
}
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(30);
pub(crate) struct Run {
client: Client,
response: Arc<JobResponse>,
log_offset: usize,
interval: Interval,
last_alive: Instant,
jobdata: RunListEntry<u64, JobData>,
}
impl Run {
pub(crate) fn new(
client: Client,
response: JobResponse,
run_list: &mut RunList<u64, JobData>,
) -> Self {
let response = Arc::new(response);
let now = Instant::now();
let jobdata = JobData::new();
let jobdata = run_list.insert(response.id, jobdata);
Self {
client,
response,
log_offset: 0,
interval: Self::create_interval(now, Duration::from_secs(3)),
last_alive: now,
jobdata,
}
}
fn create_interval(instant: Instant, period: Duration) -> Interval {
let mut interval = interval_at(instant + period, period);
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
interval
}
async fn update(&self, state: JobState) -> Result<(), crate::client::Error> {
self.client
.update_job(self.response.id, &self.response.token, state)
.await?;
Ok(())
}
async fn send_trace(&mut self, buf: Bytes) -> Result<Option<Duration>, crate::client::Error> {
assert!(!buf.is_empty());
let len = buf.len();
let reply = self
.client
.trace(
self.response.id,
&self.response.token,
buf,
self.log_offset,
len,
)
.await?;
self.log_offset += len;
Ok(reply.trace_update_interval)
}
#[tracing::instrument(skip(self, process,build_dir),fields(gitlab.job=self.response.id))]
pub(crate) async fn run<F, J, Ret>(&mut self, process: F, build_dir: PathBuf)
where
F: FnOnce(Job) -> Ret + Send + Sync + 'static,
J: JobHandler + 'static,
Ret: Future<Output = Result<J, ()>> + Send + 'static,
{
let job = Job::new(
self.client.clone(),
self.response.clone(),
build_dir.clone(),
self.jobdata.clone(),
);
let join = tokio::spawn(
run(
job,
self.client.clone(),
self.response.clone(),
process,
build_dir,
)
.in_current_span()
.with_current_subscriber(),
);
tokio::pin!(join);
let result = loop {
tokio::select! {
_ = self.interval.tick() => {
let now = Instant::now();
if let Some(buf) = self.jobdata.split_trace() {
if let Ok(Some(interval)) = self.send_trace(buf).await {
if interval != self.interval.period() {
self.interval = Self::create_interval(now, interval);
}
}
self.last_alive = now;
} else if now - self.last_alive > KEEPALIVE_INTERVAL {
let _ = self.update(JobState::Running).await;
self.last_alive = now;
}
},
r = &mut join => break r
}
};
if let Some(buf) = self.jobdata.split_trace() {
let _ = self.send_trace(buf).await.ok();
}
let state = match result {
Ok(Ok(_)) => JobState::Success,
Ok(Err(_)) => JobState::Failed,
Err(_) => JobState::Failed,
};
self.update(state).await.expect("Failed to update");
}
}