This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

.github.ci: hand the lazy store's fetch list from the eval job over

In the benchmark run (PR 68) the neoprism job spent 29 s between
fetching lazy-store and mounting: the dry-run for the paths to
substitute (13 s in the shared job for the same machine) and their
narinfo closure (the rest, 2,878 paths), both after the 21 s hestia
prefetch of the drvs, which the dry-run needs.

The eval job has the drvs and dry-runs the machines anyway for the
shared derivations; it now keeps each machine's "will be fetched" list
and uploads it with the shared ones. The machine job collects the
closure of that list in the background while hestia prefetches the
drvs, then mounts. Computing the closure in the eval job too would only
move its time onto the eval job, before every machine job starts.

For a single machine the eval job now dry-runs it (about 13 s), against
about 29 s less in the machine job; with more machines the dry-runs ran
already, and in parallel.

author
Hermes
committer
lassulus
date (Oct 17, 2026, 11:40 AM CEST) commit c3fad322 parent 7d3f6b7c
+61 -37
+10 -5
.github/ci-shared.sh
··· 2 2 # For the eval job of ci.yml: dry-run every matrix row, in parallel, and list 3 3 # the derivations that more than one row would build (not substitutable from 4 4 # any configured substituter). Usage: ci-shared.sh MATRIX_JSON OUTDIR 5 - # Writes OUTDIR/<name>.drvs per row, OUTDIR/shared-all.drvs and the 6 - # substitutable subset OUTDIR/shared.drvs. 5 + # Writes per row OUTDIR/<name>.drvs and OUTDIR/<name>.fetch, the paths it 6 + # would substitute (for .github/lazy-store.sh), OUTDIR/shared-all.drvs and 7 + # the substitutable subset OUTDIR/shared.drvs. 7 8 set -euo pipefail 8 9 matrix=$1 9 10 out=$2 ··· 16 17 cat "$out/$name.plan" 17 18 return 1 18 19 } 19 - # the drv lines below the "will be built:" header, up to the next header 20 - awk '/^[^ ]/ { b = /will be built:$/; next } b && $1 ~ /^\/nix\/store\// { print $1 }' \ 21 - "$out/$name.plan" | sort -u >"$out/$name.drvs" 20 + section 'will be built:$' <"$out/$name.plan" >"$out/$name.drvs" 21 + section 'will be fetched' <"$out/$name.plan" >"$out/$name.fetch" 22 + } 23 + 24 + # the path lines below the header matching $1, up to the next header 25 + section() { 26 + awk -v h="$1" '/^[^ ]/ { f = $0 ~ h; next } f && $1 ~ /^\/nix\/store\// { print $1 }' | sort -u 22 27 } 23 28 24 29 pids=()
+26 -16
.github/lazy-store.sh
··· 1 1 #!/usr/bin/env bash 2 - # Mounts a lazy /nix/store for building INSTALLABLE... (the machine jobs of 2 + # Mounts a lazy /nix/store for building a machine (the machine jobs of 3 3 # ci.yml): every store path the build would substitute from cache.nixos.org 4 4 # or cache.lassul.us is registered as valid from its narinfo and served by 5 5 # lazy-store (tools/lazy-store), which downloads a path's NAR only when a ··· 7 7 # is built or substituted (also from hestia) as usual; writes go to the 8 8 # real store dir below the mount. 9 9 # 10 + # `closure FETCH` collects the closure of the paths in FETCH, those the 11 + # build would substitute (from the eval job's dry-run, .github/ci-shared.sh), 12 + # from the caches' narinfos; it needs no drvs and can run while they are 13 + # fetched. `mount` mounts it on /nix/store and registers the paths, after 14 + # `closure` and once the drvs are in the store. 15 + # 10 16 # Needs sudo, /dev/fuse and LAZY_STORE, the lazy-store package's out path 11 17 # (substitutable). If anything fails before the paths are registered, the 12 18 # mount is undone and the build substitutes eagerly: this script always 13 19 # exits 0. The FUSE log ends up in $RUNNER_TEMP/lazy-store/fuse.log. 14 - # Usage: lazy-store.sh INSTALLABLE... 20 + # Usage: lazy-store.sh closure FETCH 21 + # lazy-store.sh mount 15 22 set -euo pipefail 16 23 L=$RUNNER_TEMP/lazy-store 17 24 mkdir -p "$L" ··· 19 26 fallback() { 20 27 set +e 21 28 echo "::error::lazy store: $1; the build substitutes eagerly instead" 29 + rm -f "$L/ready" 22 30 if [ -s "$L/fuse.log" ]; then 23 31 tail -n 50 "$L/fuse.log" 24 32 fi ··· 32 40 } 33 41 trap 'fallback "line $LINENO failed"' ERR 34 42 35 - nix-store --realise "$LAZY_STORE" >/dev/null || fallback "cannot get $LAZY_STORE" 36 43 lazy=$LAZY_STORE/bin/lazy-store 37 - nixbin=$(dirname "$(readlink -f "$(command -v nix-store)")") 38 44 39 - nix build --dry-run "$@" 2>"$L/plan" || fallback "nix build --dry-run failed: $(cat "$L/plan")" 40 - # the path lines below the "will be fetched" header, up to the next header 41 - awk '/^[^ ]/ { f = /will be fetched/; next } f && $1 ~ /^\/nix\/store\// { print $1 }' \ 42 - "$L/plan" >"$L/fetch" 43 - if [ ! -s "$L/fetch" ]; then 44 - echo "lazy store: nothing to substitute" 45 + if [ "$1" = closure ]; then 46 + if [ ! -s "$2" ]; then 47 + echo "lazy store: nothing to substitute" 48 + exit 0 49 + fi 50 + nix-store --realise "$LAZY_STORE" >/dev/null || fallback "cannot get $LAZY_STORE" 51 + "$lazy" closure "$L" <"$2" >/dev/null || fallback "closure failed" 52 + jq -r '"lazy store: \(.paths) of the \(.roots) paths to substitute and their closure (\(.file_size / 1048576 | floor) MiB to download eagerly); \(.missing | length) not in the caches, \(.incomplete) referring to those"' \ 53 + "$L/closure.json" 54 + if [ "$(jq length "$L/paths.json")" != 0 ]; then 55 + touch "$L/ready" 56 + fi 45 57 exit 0 46 58 fi 47 59 48 - "$lazy" closure "$L" <"$L/fetch" >/dev/null || fallback "closure failed" 49 - jq -r '"lazy store: \(.paths) of the \(.roots) paths to substitute and their closure (\(.file_size / 1048576 | floor) MiB to download eagerly); \(.missing | length) not in the caches, \(.incomplete) referring to those"' \ 50 - "$L/closure.json" 60 + if [ ! -e "$L/ready" ]; then 61 + exit 0 62 + fi 51 63 n=$(jq length "$L/paths.json") 52 - if [ "$n" = 0 ]; then 53 - exit 0 54 - fi 64 + nixbin=$(dirname "$(readlink -f "$(command -v nix-store)")") 55 65 56 66 sudo modprobe fuse || true 57 67 # The upper layer is the real store dir, reached through a private bind
+25 -16
.github/workflows/ci.yml
··· 131 131 run: | 132 132 echo "not cached: $(jq -r '[.include[].name] | join(" ")' <<<"$MATRIX")" | 133 133 tee -a "$GITHUB_STEP_SUMMARY" 134 - # the derivations the shared job builds, none for a single machine 134 + # the derivations the shared job builds and per machine the paths its 135 + # lazy store registers 135 136 - id: plan 136 137 if: steps.matrix.outputs.any-jobs == 'true' 137 138 env: ··· 140 141 matrix=$RUNNER_TEMP/matrix.json 141 142 plans=$RUNNER_TEMP/plans 142 143 printf '%s' "$MATRIX" > "$matrix" 143 - mkdir -p "$plans" 144 - : > "$plans/shared.drvs" 145 - if [ "$(jq '.include | length' "$matrix")" -ge 2 ]; then 146 - .github/ci-shared.sh "$matrix" "$plans" 147 - { 148 - for name in $(jq -r '.include[].name' "$matrix"); do 149 - echo "- $name: $(wc -l < "$plans/$name.drvs") derivations to build" 150 - done 151 - echo "- shared (needed by >= 2 machines): $(wc -l < "$plans/shared.drvs")" 152 - sed -E 's|^/nix/store/[^-]+-(.*)\.drv$| \1|' "$plans/shared.drvs" 153 - } | tee -a "$GITHUB_STEP_SUMMARY" 154 - fi 144 + .github/ci-shared.sh "$matrix" "$plans" 145 + { 146 + for name in $(jq -r '.include[].name' "$matrix"); do 147 + echo "- $name: $(wc -l < "$plans/$name.drvs") derivations to build" 148 + done 149 + echo "- shared (needed by >= 2 machines): $(wc -l < "$plans/shared.drvs")" 150 + sed -E 's|^/nix/store/[^-]+-(.*)\.drv$| \1|' "$plans/shared.drvs" 151 + } | tee -a "$GITHUB_STEP_SUMMARY" 155 152 if [ -s "$plans/shared.drvs" ]; then 156 153 echo "shared=true" >> "$GITHUB_OUTPUT" 157 154 fi 158 - - if: steps.plan.outputs.shared == 'true' 155 + - if: steps.matrix.outputs.any-jobs == 'true' 159 156 uses: actions/upload-artifact@v4 160 157 with: 161 158 name: plans 162 - path: ${{ runner.temp }}/plans/shared.drvs 159 + path: | 160 + ${{ runner.temp }}/plans/shared.drvs 161 + ${{ runner.temp }}/plans/*.fetch 163 162 164 163 shared: 165 164 needs: eval ··· 241 240 run: | 242 241 nix-store --realise "$NIKS3" 243 242 echo "$NIKS3/bin" >> "$GITHUB_PATH" 243 + - uses: actions/download-artifact@v4 244 + with: 245 + name: plans 246 + path: ${{ runner.temp }}/plans 244 247 - name: build ${{ matrix.name }} 245 248 env: 249 + NAME: ${{ matrix.name }} 246 250 INSTALLABLES: ${{ matrix.installables }} 247 251 LAZY_STORE: ${{ needs.eval.outputs.lazy-store }} 248 252 run: | 249 253 read -ra installables <<<"$INSTALLABLES" 254 + # the closure needs only the caches, so it runs while the drvs are 255 + # fetched 256 + .github/lazy-store.sh closure "$RUNNER_TEMP/plans/$NAME.fetch" & 257 + closure=$! 250 258 "$HESTIA_BIN" prefetch "${installables[@]}" 251 - .github/lazy-store.sh "${installables[@]}" 259 + wait "$closure" 260 + .github/lazy-store.sh mount 252 261 .github/ci-build.sh "${installables[@]}" 253 262 - name: lazy store log 254 263 if: always()