~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

A release waits after the deploy and runs on the runner handed over to

Closed chadtmiller.com asked to merge into main from chadtmiller.com/knotless:run/release-after-deploy
.changeset/run-after-deploy-hands-over.mdnew+7−0
@@ -0,0 +1,7 @@
---
default: patch
---
# A run waiting after a deploy runs on the runner it hands over to
A runner stopping keeps a run at the same commit as a deploy going, to run itself, since the deploy may wait for it. A run whose workflow waits `after` that deploy is now dropped instead, and the runner started next runs it once the deploy has passed, rather than the stopping one starting it and cutting it off past `--stop-wait`.
.knotless/workflows/release.yml+7−0
@@ -11,10 +11,17 @@
# main alone in the runner's secrets file. Asked for on any other branch,
# `knotless rerun <repo> --ref refs/heads/<branch> --workflow release`, it
# builds everything and publishes nothing, to try the build.
#
# It runs after deploy.yml at its commit. A version bump changes every
# binary, so the deploy hands the runner over to a new one, and the old
# one cuts off what it still runs 15 minutes after (--stop-wait), less
# than this build takes. Waiting after the deploy, it is dropped by the
# old runner and run by the new one.
on:
push: {refs: [refs/heads/main], paths: [Cargo.toml]}
asked: {refs: [refs/heads/*]}
superseded: finish
after: [deploy]
image: rust:1.98
cache: [target, /usr/local/cargo/registry, /usr/local/rustup, /opt/tools]
secrets: [RELEASE_PASSWORD]
crates/knotless-run/src/lib.rs+19−10
@@ -1028,8 +1028,8 @@ pub struct Runner {
/// checks' keys: any other of this runner's is left over, and removed.
sessions: Mutex<HashSet<String>>,
/// Where each run going of a workflow whose runs finish is at, as
/// [`Runner::dropping`] names it.
finishing: tokio::sync::watch::Sender<Vec<String>>,
/// [`Runner::dropping`] names it, and its workflow.
finishing: tokio::sync::watch::Sender<Vec<(String, String)>>,
/// The appview runs' output goes to as it is written, and writes in
/// private repositories' spaces come from ([`live`]).
appview: Option<Arc<live::Appview>>,
@@ -1083,13 +1083,14 @@ impl Drop for Deciding {
/// A run going of a workflow whose runs finish, noted in
/// [`Runner::finishing`] until it is dropped.
struct Finishing<'a> {
finishing: &'a tokio::sync::watch::Sender<Vec<String>>,
of: String,
finishing: &'a tokio::sync::watch::Sender<Vec<(String, String)>>,
of: (String, String),
}
impl<'a> Finishing<'a> {
fn new(runner: &'a Runner, of: String) -> Self {
fn new(runner: &'a Runner, of: String, workflow: &str) -> Self {
let finishing = &runner.finishing;
let of = (of, workflow.to_string());
finishing.send_modify(|all| all.push(of.clone()));
Finishing { finishing, of }
}
@@ -2316,7 +2317,7 @@ impl Runner {
let waits = self.waits(rank, watch, subject, wf, on_ref, &mut cancelled);
let waited = tokio::select! {
biased;
_ = self.dropping(&of, batch, &mut deciding) => {
_ = self.dropping(&of, batch, &wf.after, &mut deciding) => {
telemetry::run("run dropped, to run on the next start", watch, subject, &wf.name, None, None);
return self.drop_run(watch, subject, wf, rkey, check).await;
}
@@ -2356,7 +2357,8 @@ impl Runner {
let began = std::time::Instant::now();
telemetry::run("run started", watch, subject, &wf.name, None, None);
let started = Started {
finishing: (wf.superseded == Superseded::Finish).then(|| Finishing::new(self, of)),
finishing: (wf.superseded == Superseded::Finish)
.then(|| Finishing::new(self, of, &wf.name)),
job,
cancelled,
said,
@@ -2992,11 +2994,18 @@ impl Runner {
/// stopping, unless it is a `batch`'s, or a run going whose runs
/// finish, a deploy, is at the same place, which may wait for its
/// commit's checks. Those are kept, `deciding` so, to run here: the
/// runner handed over to knows them as this one's.
async fn dropping(&self, of: &str, batch: bool, deciding: &mut Deciding) {
/// runner handed over to knows them as this one's. A run that waits
/// `after` that deploy is dropped all the same: the deploy is what
/// stops this runner, so the one handed over to runs it, rather than
/// this one cutting it off past its stop's wait.
async fn dropping(&self, of: &str, batch: bool, after: &[String], deciding: &mut Deciding) {
let mut stopping = self.stopping.subscribe();
let _ = stopping.wait_for(|stopping| *stopping).await;
let kept = self.finishing.borrow().iter().any(|f| f == of);
let kept = {
let finishing = self.finishing.borrow();
let mut here = finishing.iter().filter(|(at, _)| at == of);
here.any(|(_, wf)| !after.contains(wf))
};
if !batch && !kept {
return;
}
crates/knotless-run/tests/run.rs+12−1
@@ -1516,8 +1516,14 @@ fn a_stop_drops_what_has_not_started() {
"on: {{push: {{refs: [refs/heads/main]}}, pull: {{base: [refs/heads/main]}}}}\nsteps:\n - run: |\n echo \"$KNOTLESS_RUN_SHA ${{KNOTLESS_RUN_PULL:-push}}\" >> {go}/thens\n until [ -f {go}/then ]; do sleep 0.1; done\n",
go = go.display()
);
// `release` waits for the deploy, which is what stops a runner.
let release = format!(
"superseded: finish\non: {{push: {{refs: [refs/heads/main]}}}}\nafter: [deploy]\nsteps:\n - run: echo $KNOTLESS_RUN_SHA >> {go}/releases\n",
go = go.display()
);
std::fs::write(dir.join("deploy.yml"), deploy).unwrap();
std::fs::write(dir.join("then.yml"), then).unwrap();
std::fs::write(dir.join("release.yml"), release).unwrap();
owner.git(&work, &["add", "."]);
owner.git(&work, &["commit", "-q", "-m", "workflows"]);
owner.git(&work, &["push", "-q", "origin", "HEAD:block"]);
@@ -1554,15 +1560,18 @@ fn a_stop_drops_what_has_not_started() {
of(&pull, "then")["status"] == "queued"
});
assert_eq!(of("refs/heads/main", "then")["status"], "queued");
assert_eq!(of("refs/heads/main", "release")["status"], "queued");
// Stopped, it drops the pull's run and reads no more, but waits for
// the deploy going, and keeps the push's run of its commit, which a
// deploy may wait for, to run here once the deploy ends.
// deploy may wait for, to run here once the deploy ends. The run
// waiting after the deploy it drops, for the next runner to run.
let stopping = rt.spawn({
let run = run.clone();
async move { run.stop().await }
});
wait_until(|| of(&pull, "then")["status"] == "cancelled");
wait_until(|| of("refs/heads/main", "release")["status"] == "cancelled");
rt.block_on(run.reconcile(&watch)).unwrap();
std::thread::sleep(Duration::from_millis(500));
assert!(!stopping.is_finished());
@@ -1582,6 +1591,8 @@ fn a_stop_drops_what_has_not_started() {
assert_eq!(of(&pull, "then")["status"], "success");
assert_eq!(ran("thens"), format!("{main} push\n{topic} {pull}\n"));
assert_eq!(ran("deploys"), format!("{main}\n"));
assert_eq!(of("refs/heads/main", "release")["status"], "success");
assert_eq!(ran("releases"), format!("{main}\n"));
}
/// A runner stopping hands over to the one started beside it: the new one
docs/design/runner.md+5−1
@@ -514,7 +514,11 @@ check is filed as `cancelled`, and the state file records it. Two kinds
are kept instead, to start here as jobs free: a merge-queue batch's,
since dropping one of a batch's runs has the whole batch checked again,
and one at the same commit and base as a running `superseded: finish`
run, since a deploy may wait for its own commit's other checks.
run, since a deploy may wait for its own commit's other checks. A run
that waits `after` that one is dropped all the same: a deploy is what
stops the runner, so a run after it is left to the next runner, which
starts it once the deploy has passed, rather than started here and cut
off past `--stop-wait`.
On the next start, a dropped run runs again for only those workflows, and
only if its ref or pull is still where it was. If it moved on, the newer