Skip to content

Commit 3189d47

Browse files
committed
various: fix evaluator restart handling for inactive jobsets
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I18c89d26f662d132783545f9ed13582a6a6a6964
1 parent 012b81a commit 3189d47

4 files changed

Lines changed: 56 additions & 9 deletions

File tree

crates/common/src/repo/evaluations.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -366,17 +366,20 @@ pub async fn cancel(pool: &PgPool, id: Uuid) -> Result<Option<Evaluation>> {
366366
}
367367

368368
/// Requeue a cancelled, failed, or timed-out evaluation after discarding its
369-
/// stale builds. One-shot jobsets are re-enabled for the new attempt.
369+
/// stale builds. A disabled jobset rejects the restart; a retained one-shot
370+
/// jobset is re-enabled for its new attempt.
370371
///
371372
/// # Errors
372373
///
373374
/// Returns an error if the database transaction fails.
374375
pub async fn restart(pool: &PgPool, id: Uuid) -> Result<Option<Evaluation>> {
375376
let mut tx = pool.begin().await?;
376377
let evaluation = sqlx::query_as::<_, Evaluation>(
377-
"UPDATE evaluations SET status = 'pending', evaluation_time = NOW(), \
378-
error_message = NULL, inputs_hash = NULL WHERE id = $1 AND status IN \
379-
('cancelled', 'failed', 'timed_out') RETURNING *",
378+
"UPDATE evaluations e SET status = 'pending', evaluation_time = NOW(), \
379+
error_message = NULL, inputs_hash = NULL FROM jobsets j WHERE e.id = $1 \
380+
AND e.jobset_id = j.id AND e.status IN ('cancelled', 'failed', \
381+
'timed_out') AND (j.state = 'one_shot' OR (j.enabled AND j.state IN \
382+
('enabled', 'one_at_a_time'))) RETURNING e.*",
380383
)
381384
.bind(id)
382385
.fetch_optional(&mut *tx)

crates/common/tests/repo_tests.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,44 @@ async fn test_restarting_one_shot_evaluation_resets_its_attempt() {
593593
assert_eq!(restarted_jobset.state, JobsetState::OneShot);
594594
}
595595

596+
#[tokio::test]
597+
async fn test_restart_rejects_a_manually_disabled_jobset() {
598+
let Some(pool) = get_pool().await else {
599+
return;
600+
};
601+
602+
let project = create_test_project(&pool, "restart-disabled").await;
603+
let jobset = create_test_jobset(&pool, project.id).await;
604+
let eval = create_test_eval(&pool, jobset.id).await;
605+
repo::evaluations::update_status(
606+
&pool,
607+
eval.id,
608+
EvaluationStatus::Failed,
609+
Some("failed"),
610+
)
611+
.await
612+
.expect("fail evaluation");
613+
sqlx::query("UPDATE jobsets SET enabled = false WHERE id = $1")
614+
.bind(jobset.id)
615+
.execute(&pool)
616+
.await
617+
.expect("disable jobset");
618+
619+
assert!(
620+
repo::evaluations::restart(&pool, eval.id)
621+
.await
622+
.expect("attempt restart")
623+
.is_none()
624+
);
625+
assert_eq!(
626+
repo::evaluations::get(&pool, eval.id)
627+
.await
628+
.expect("get failed evaluation")
629+
.status,
630+
EvaluationStatus::Failed
631+
);
632+
}
633+
596634
#[tokio::test]
597635
async fn test_cancellation_cannot_interleave_build_persistence() {
598636
let Some(pool) = get_pool().await else {

crates/server/src/routes/dashboard/admin.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,8 @@ pub(super) async fn evaluation_restart(
770770
.ok_or_else(|| {
771771
(
772772
StatusCode::CONFLICT,
773-
"Only failed or cancelled evaluations can be restarted",
773+
"Only failed, cancelled, or timed-out evaluations with active jobsets \
774+
can be restarted",
774775
)
775776
.into_response()
776777
})?;

crates/server/templates/evaluation.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,18 @@ <h1>Evaluation {{ eval.commit_short }}</h1>
3939
{% endif %}
4040
</form>
4141
{% if eval.status_class == "running" || eval.status_class == "pending" %}
42-
<form class="inline" method="POST" action="/evaluation/{{ eval.id }}/cancel"
42+
<form class="inline"
43+
method="POST"
44+
action="/evaluation/{{ eval.id }}/cancel"
4345
onsubmit="return confirm('Cancel this evaluation?')">
4446
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
4547
<button class="btn btn-small btn-danger" type="submit">Cancel Evaluation</button>
4648
</form>
4749
{% endif %}
48-
{% if eval.status_class == "failed" || eval.status_class == "cancelled" %}
49-
<form class="inline" method="POST" action="/evaluation/{{ eval.id }}/restart"
50+
{% if eval.status_class == "failed" || eval.status_class == "cancelled" || eval.status_class == "timed-out" %}
51+
<form class="inline"
52+
method="POST"
53+
action="/evaluation/{{ eval.id }}/restart"
5054
onsubmit="return confirm('Restart this evaluation and discard its current builds?')">
5155
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
5256
<button class="btn btn-small btn-secondary" type="submit">Restart Evaluation</button>
@@ -71,7 +75,8 @@ <h1>Evaluation {{ eval.commit_short }}</h1>
7175
<h2>Evaluation Error</h2>
7276
<span>Nix diagnostics</span>
7377
</div>
74-
<pre class="error-detail">{{ eval.error_message }}</pre>
78+
<pre class="error-detail">
79+
{{ eval.error_message }}</pre>
7580
</section>
7681
{% endif %}
7782

0 commit comments

Comments
 (0)