Record builds interrupted by a server shutdown as INTERRUPTED, not FAILED

A systemd stop killed the running build process and BuildExecutor
classified the death as FAILED, posting a red Gitea status; FAILED is
not restartable, so the startup recovery never re-enqueued the build.

A ContextClosedEvent listener now sets a shuttingDown flag, terminates
the process trees of executing builds, and drains until their
INTERRUPTED results are persisted. Queued builds stay PENDING without
starting a process; recovery re-enqueues both after the restart.
INTERRUPTED publishes as Gitea state "pending" instead of "failure",
since the build is going to be re-run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-10 17:26:10 +02:00
co-authored by Claude Fable 5
parent fbdc6f54b7
commit 40f54a1298
7 changed files with 236 additions and 20 deletions
@@ -2,12 +2,17 @@ package de.hoennig.gittally.gitea
import de.hoennig.gittally.build.BuildStatus
/** Gitea commit-status state published for this build status. */
/**
* Gitea commit-status state published for this build status.
* INTERRUPTED publishes as `pending`, not `failure`: an interrupted build (e.g. a
* server shutdown) is re-enqueued by the startup recovery, so the commit must not
* turn red in between.
*/
fun BuildStatus.toGiteaState(): String =
when (this) {
BuildStatus.SUCCESS -> "success"
BuildStatus.FAILED, BuildStatus.INTERRUPTED, BuildStatus.CANCELLED -> "failure"
BuildStatus.PENDING, BuildStatus.RUNNING -> "pending"
BuildStatus.FAILED, BuildStatus.CANCELLED -> "failure"
BuildStatus.PENDING, BuildStatus.RUNNING, BuildStatus.INTERRUPTED -> "pending"
}
/**