Prepare server template assembly alongside qualification (#31422)

* Prepare template assembly during qualification and cache Intel downloads

* Limit early template qualification polling to fifteen seconds

* Verify Windows retains its download cache policy

* Drop Intel store restoration after measuring its overhead
This commit is contained in:
Neil
2026-10-21 00:06:40 -07:00
committed by GitHub
parent 5fc6cb46b0
commit 73930bedee
5 changed files with 164 additions and 31 deletions
+44 -6
View File
@@ -563,12 +563,11 @@ jobs:
# Design D2: the desktop ships every target's addons, merged from the lanes that qualified them.
desktop_template:
needs: [persistence, mobile_web_bundle, linux_glibc_floor, linux_glibc217_compat, linux_musl]
if: >-
${{ !cancelled() && inputs.build_template &&
needs.persistence.result == 'success' && needs.mobile_web_bundle.result == 'success' &&
needs.linux_glibc_floor.result == 'success' &&
needs.linux_glibc217_compat.result == 'success' && needs.linux_musl.result == 'success' }}
needs: changes
if: ${{ !cancelled() && inputs.build_template }}
permissions:
contents: read
actions: read
runs-on: ${{ inputs.fast_runners && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
@@ -577,6 +576,45 @@ jobs:
ref: ${{ inputs.ref }}
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
- name: Wait for every template qualification lane
uses: actions/github-script@v8
with:
script: |
const deadline = Date.now() + 25 * 60_000
const expected = {
persistence: 6,
mobile_web_bundle: 1,
linux_glibc_floor: 2,
linux_glibc217_compat: 1,
linux_musl: 2
}
while (Date.now() < deadline) {
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
...context.repo, run_id: context.runId, filter: 'latest', per_page: 100
})
const assemblers = jobs.filter(job =>
job.name === 'desktop_template' || job.name.endsWith(' / desktop_template'))
if (assemblers.length > 1) throw new Error('Ambiguous desktop template qualification')
if (assemblers.length === 1) {
const prefix = assemblers[0].name.slice(0, -'desktop_template'.length)
let ready = true
for (const [name, count] of Object.entries(expected)) {
const lanes = jobs.filter(job => job.name === `${prefix}${name}` ||
job.name.startsWith(`${prefix}${name} (`))
if (lanes.length > count) throw new Error(`Unexpected qualification lanes: ${name}`)
for (const lane of lanes) {
if (lane.status === 'completed' && lane.conclusion !== 'success') {
throw new Error(`Template qualification did not succeed: ${lane.name} (${lane.conclusion})`)
}
}
if (lanes.length !== count || lanes.some(lane => lane.status !== 'completed')) ready = false
}
if (ready) return
}
await new Promise(resolve => setTimeout(resolve, 15_000))
}
throw new Error('Timed out waiting for every template qualification lane')
- name: Collect every lane's slot
uses: actions/download-artifact@v8
with:
@@ -73,28 +73,23 @@ it('starts independent Linux qualification without waiting for another platform'
}
})
it('requires every platform and compatibility result before publishing a desktop template', () => {
it('prepares template dependencies before waiting for every qualification lane', () => {
const template = workflow.jobs.desktop_template
const required = [
'persistence',
'mobile_web_bundle',
'linux_glibc_floor',
'linux_glibc217_compat',
'linux_musl'
]
expect(template.needs).toEqual(required)
const evaluate = compileAggregateExpression(template.if, ['needs', 'inputs'])
const needs = Object.fromEntries(required.map((name) => [name, { result: 'success' }]))
const ctx = { needs, inputs: { build_template: true } }
expect(evaluate(ctx).coerceString()).toBe('true')
expect(evaluate(ctx, true).coerceString()).toBe('false')
for (const name of required) {
for (const result of ['failure', 'cancelled', 'skipped']) {
expect(evaluate({ ...ctx, needs: { ...needs, [name]: { result } } }).coerceString()).toBe(
'false'
)
}
}
expect(template.needs).toBe('changes')
expect(template.permissions).toEqual({ contents: 'read', actions: 'read' })
const evaluate = compileAggregateExpression(template.if, ['inputs'])
expect(evaluate({ inputs: { build_template: true } }).coerceString()).toBe('true')
expect(evaluate({ inputs: { build_template: false } }).coerceString()).toBe('false')
expect(evaluate({ inputs: { build_template: true } }, true).coerceString()).toBe('false')
const wait = template.steps.findIndex(
(step) => step.name === 'Wait for every template qualification lane'
)
expect(
template.steps.findIndex((step) => step.uses?.includes('install-node-dependencies'))
).toBeLessThan(wait)
expect(
template.steps.findIndex((step) => step.name === "Collect every lane's slot")
).toBeGreaterThan(wait)
})
it('avoids template download-cache transfers while retaining native and verification caches', () => {
@@ -106,7 +101,11 @@ it('avoids template download-cache transfers while retaining native and verifica
for (const os of ['Linux', 'macOS', 'Windows']) {
for (const buildTemplate of [true, false]) {
expect(
runInNewContext(source, { inputs: { build_template: buildTemplate }, runner: { os } })
runInNewContext(source, {
inputs: { build_template: buildTemplate, fast_runners: false },
matrix: { os: '' },
runner: { os }
})
).toBe(!buildTemplate && os !== 'Windows')
}
}
@@ -65,8 +65,13 @@ it('ships the page the qualification lanes tested in the release template', () =
readFileSync(new URL('../../.github/workflows/node-server-tests.yml', import.meta.url), 'utf8')
)
const template = workflow.jobs.desktop_template
expect(template.needs).toContain('mobile_web_bundle')
expect(template.if).toContain("needs.mobile_web_bundle.result == 'success'")
const wait = template.steps.findIndex(
(step) => step.name === 'Wait for every template qualification lane'
)
expect(template.steps[wait].with.script).toContain('mobile_web_bundle: 1')
expect(wait).toBeLessThan(
template.steps.findIndex((step) => step.with?.name === 'orcad-mobile-web-bundle')
)
expect(template.steps.some((step) => step.with?.name === 'orcad-mobile-web-bundle')).toBe(true)
expect(template.steps.some((step) => step.uses?.includes('build-mobile-web-bundle'))).toBe(false)
})
@@ -32,7 +32,7 @@ const EXPECTED_MATRIX = {
'persistence'
].map((job) => [
`.github/workflows/release-mac-build.yml#qualification-template -> .github/workflows/qualified-orcad-template.yml#qualify -> .github/workflows/node-server-tests.yml#${job}`,
{ contents: 'read' }
job === 'desktop_template' ? { actions: 'read', contents: 'read' } : { contents: 'read' }
])
),
...Object.fromEntries(
@@ -68,6 +68,7 @@ const EXPECTED_MATRIX = {
},
[`${RELEASE_WORKFLOW}#orcad-template -> .github/workflows/qualified-orcad-template.yml#qualify -> .github/workflows/node-server-tests.yml#desktop_template`]:
{
actions: 'read',
contents: 'read'
},
[`${RELEASE_WORKFLOW}#orcad-template -> .github/workflows/qualified-orcad-template.yml#qualify -> .github/workflows/node-server-tests.yml#linux_glibc217_compat`]:
@@ -0,0 +1,90 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
const { jobs } = parse(readFileSync('.github/workflows/node-server-tests.yml', 'utf8'))
const step = jobs.desktop_template.steps.find(
(item) => item.name === 'Wait for every template qualification lane'
)
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor
function lanes(prefix = 'orcad-template / qualify / ') {
return [
{ name: `${prefix}desktop_template`, status: 'in_progress' },
...Object.entries({
persistence: 6,
mobile_web_bundle: 1,
linux_glibc_floor: 2,
linux_glibc217_compat: 1,
linux_musl: 2
}).flatMap(([name, count]) =>
Array.from({ length: count }, (_, i) => ({
name: `${prefix}${name}${count > 1 ? ` (platform-${i})` : ''}`,
status: 'completed',
conclusion: 'success'
}))
)
]
}
function execute(snapshots) {
let now = 0
let index = 0
const calls = []
const github = {
rest: { actions: { listJobsForWorkflowRun: 'jobs' } },
async paginate(endpoint, input) {
calls.push({ endpoint, input })
return snapshots[Math.min(index, snapshots.length - 1)]
}
}
return {
calls,
result: new AsyncFunction('github', 'context', 'Date', 'setTimeout', step.with.script)(
github,
{ repo: { owner: 'owner', repo: 'repo' }, runId: 42 },
{ now: () => now },
(resolve) => {
now += 5000
index++
resolve()
}
)
}
}
describe('early template assembly qualification gate', () => {
it.each(['', 'orcad-template / qualify / '])(
'waits for all current-attempt jobs with prefix %s',
async (prefix) => {
const incomplete = lanes(prefix)
incomplete[1] = { ...incomplete[1], status: 'in_progress', conclusion: null }
const { calls, result } = execute([incomplete, lanes(prefix)])
await result
expect(calls).toHaveLength(2)
expect(calls.every(({ input }) => input.filter === 'latest' && input.run_id === 42)).toBe(
true
)
}
)
it.each(['failure', 'cancelled', 'skipped', 'timed_out', 'action_required', 'startup_failure'])(
'rejects %s from any lane',
async (conclusion) => {
for (let i = 1; i < lanes().length; i++) {
const input = lanes()
input[i].conclusion = conclusion
await expect(execute([input]).result).rejects.toThrow('qualification did not succeed')
}
}
)
it('fails closed for missing, extra, ambiguous and unrelated jobs', async () => {
await expect(execute([lanes().slice(0, -1)]).result).rejects.toThrow('Timed out')
await expect(
execute([[...lanes(), { ...lanes()[1], name: `${lanes()[1].name}-extra` }]]).result
).rejects.toThrow('Unexpected')
await expect(execute([[...lanes(), ...lanes('other / ')]]).result).rejects.toThrow('Ambiguous')
await expect(
execute([
[{ name: 'desktop_template', status: 'in_progress' }, ...lanes('unrelated / ').slice(1)]
]).result
).rejects.toThrow('Timed out')
})
})