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

chadtmiller.com/knotless

git that lives in your Atmosphere account

7
114 lines · 6.3 KB Raw History

knotless-run internals

What knotless-run keeps on disk, and the containers and volumes it makes: for operating a runner and debugging one. How it decides what to run is in the design; its flags are in the command reference.

The state file

The state file is pretty-printed JSON, written through a temporary file. Each field is keyed by repository, as <owner did>/<repo key>:

FieldHolds
reposeach ref's generation last run
pullseach open pull's URI, to the "<head> <base>" it last ran at
rerunsthe record key of the newest rerun request acted on
askedthe URIs of untrusted pulls the owner asked to run, not run yet
asked_onlyof those, each asked for only some workflows, to their names
passedeach passing trusted pull's or batch's run: tree, workflow, base ref, check key, pull, and the head and base the pull was at; oldest first, at most 256 a repository
droppedruns dropped as the runner stopped, by ref, pull URI or queue <branch ref>, to "<commit> <base>", to workflows
wantedworkflows asked for by name and not run yet, by ref, with the variables asked with

Caches on disk

Under --caches:

Path
bases/<cache>/<generation>each generation of a cache's base; <cache> is knotless-cache-<h1>-<h2>, <h1> a hash of the repository and <h2> of the workflow, mount path and, for a cache read apart, apart; a deleted repository's go
runs/<run>/<i>/{upper,work}a run's overlay layers; <run> is the runner's boot time in milliseconds and a counter
kept/<hash of check key>/a passing trusted run's kept layers: kept.json, and <i>/upper per cache
marks.jsonof each cache whose current base a trusted pull made under --pulls-only, that base's generation and the default branch's generation at the tip the pull merged with: {"<cache>": [<base>, <tip>]}

Containers and volumes

Each overlay is a docker local volume of type overlay (lowerdir=<base>,upperdir=<upper>,workdir=<work>,userxattr), named knotless-overlay-<run>-<i> and labelled dev.knotless.overlay=<run>. Each run's container is labelled dev.knotless.runner=<did> and dev.knotless.run=<check key>, runs with --init, mounts the checkout at /work and the outputs at /outputs, and, outside pushes to the default branch and the batch landing next, has --cpu-shares 256. With the runner's limits set it also has --cpus <n>, --memory <bytes> --memory-swap <bytes>, --pids-limit <n> and --network <name>; before it starts, docker network inspect <name> asks for that network, and if it is missing docker network create --driver bridge --opt com.docker.network.bridge.enable_icc=false <name> makes it, asking again when that fails, in case another run's start made it meanwhile.

Cleanup at start

At start the runner removes, before any run, containers labelled with its DID or (with --work) mounting a path under --work, volumes labelled dev.knotless.overlay, and knotless-cache-* volumes from before overlays. Runs whose overlays are still mounted keep their directories, and while any are, no old base is removed. Half-made *.new bases, and kept directories not wanted or broken, are removed, and the newest generation of each cache is the current one. A base named knotless-cache-<hash>, as runners named them before a cache's name held its repository's, is removed. Marks of a base no longer current are let go.

Free space

The free space is statvfs(2) of --caches: the blocks available to an unprivileged writer (f_bavail) times the fragment size (f_frsize). Below --disk-floor, a run about to start removes every kept/* directory and, for each cache whose current base no run reads, that base and its older generations no run reads, forgetting the cache's mark. It does this once for each run that finds the disk short. The run's log starts ==> <n> GiB free on the disk, under the runner's floor of <n> GiB: shed <n> caches' bases no run read and <n> kept runs' layers, then says ==> this one waited <n>s for room on the disk if it waited 30s or more. The runner logs disk short: shedding caches and disk short: waiting for room at warn, with the free space in reason.

What a pass logs

Each pass over a repository, as its door rings, logs pass ended with repo, status (ok or failed) and duration_ms: at debug, or at info when it took KNOTLESS_SLOW_MS (1000) or longer.

A pass still going after 5 minutes logs pass running long at warn, with repo, duration_ms and stage, where the pass last said it was, and again every 5 minutes while it goes on. The stages, in the order a pass meets them:

Stage
waiting for a stopwaiting for a stop under way, which holds every pass back
reading the repositoryreading the repository's record and refs
reading rerunsreading the reruns asked for
running refsfiling and starting the runs of refs that moved
running pullswith --pulls, reading open pulls and starting their runs
reading the queuewith --queue, reading the merge queue's records
syncing the mirrorbringing the mirror the queue builds batches in up to date
landing a batchlanding a batch whose checks passed
ejecting a pullwriting a pull's ejection
checking pre-flightsreading and starting the pre-flights of pulls waiting
stacking a batchmerging a batch's pulls onto the branch, in the mirror
launching a batchfiling and starting a batch's runs
pruning checksdeleting checks of no more use

Each git command logs git (target knotless::git), with command, its subcommand (pack-objects), path, the repository's directory, and duration_ms: at debug, or at warn when it took KNOTLESS_SLOW_MS or longer, as a slow HTTP call logs outgoing at warn. A git command is never cut short.

A failure of a repository's work logs failed at warn, with repo and error, the first time that error is seen for that repository. The same error again is logged only once a minute while it goes on, with count, the times it was seen since last logged. Another error is logged at once, and so is the same one after any of the repository's work succeeds, or once it has not been seen for an hour.