[ci] Cap concurrent Vercel E2E action repo-wide (10 by default) (#4039)

This commit is contained in:
Peter Wielander
2026-09-08 14:47:37 -07:00
committed by GitHub
parent c340820411
commit 9a9af618f7
2 changed files with 71 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
---
---
Cap how many runs' worth of Vercel E2E lanes CI executes at once, so a burst of pushes queues instead of piling onto the shared backend.
+67
View File
@@ -24,6 +24,7 @@ jobs:
outputs:
fast-path: ${{ steps.scope.outputs.runtime-fast-path }}
validation-fast-path: ${{ steps.scope.outputs.validation-fast-path }}
e2e-vercel-slot: ${{ steps.e2e-slot.outputs.slot }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
@@ -36,6 +37,37 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# Slot index for the Vercel e2e lanes' concurrency groups (see the
# `concurrency` block on e2e-vercel-prod for what the groups do). The
# slot count is how many runs' worth of Vercel e2e traffic may be in
# flight repo-wide at once; everything past that queues.
#
# The modulo happens here because GitHub expressions have no arithmetic
# operators, so the lanes can only read a precomputed value through
# `needs`. Keying on run_id rather than something random keeps a re-run
# of a run in the same slot it had, so a re-run queues behind the run it
# replaces instead of contending with a third one.
#
# Tunable without a code change: set the repository variable
# E2E_VERCEL_CONCURRENCY_SLOTS (Settings > Secrets and variables >
# Actions > Variables). Raise it if PRs are waiting too long, lower it
# to 1 to serialize the Vercel lanes across the whole repo.
- name: Pick Vercel e2e concurrency slot
id: e2e-slot
env:
SLOTS: ${{ vars.E2E_VERCEL_CONCURRENCY_SLOTS }}
RUN_ID: ${{ github.run_id }}
run: |
slots="${SLOTS:-2}"
case "$slots" in
'' | *[!0-9]*) slots=2 ;;
esac
if [ "$slots" -lt 1 ]; then
slots=1
fi
echo "slot=$((RUN_ID % slots))" >> "$GITHUB_OUTPUT"
echo "Vercel e2e lanes use slot $((RUN_ID % slots)) of $slots"
# Phase 0: Update PR comment to show tests are running
pr-comment-start:
name: Create PR Comment
@@ -319,6 +351,27 @@ jobs:
continue-on-error: ${{ matrix.app.name == 'python' }}
needs: ci-scope
if: ${{ needs.ci-scope.outputs.fast-path != 'true' }}
# Repo-wide cap on how much traffic CI can point at world-vercel at once.
#
# The group is per (lane, matrix cell, slot), not per run, so a single
# PR still fans its whole matrix out in parallel: within one run every
# cell is a distinct group. What collides is the *same* cell from a
# *different* run, which waits instead of doubling the load on the
# backend and on the shared Vercel project this cell deploys to. With
# the default two slots that leaves at most two runs' worth of Vercel
# e2e in flight repo-wide, however many PRs are pushed at once.
#
# `queue: max` is what makes this safe to apply to a required check.
# The default (`queue: single`) keeps one pending entry per group and
# cancels the rest, which would turn a burst of PRs into cancelled
# jobs and a red E2E Required Check; `max` queues up to 100 instead.
# It cannot be combined with `cancel-in-progress: true`: GitHub
# rejects the workflow. Superseded runs are still cancelled by the
# workflow-level concurrency at the top of this file, which releases
# their queued jobs too.
concurrency:
group: e2e-vercel-prod-${{ matrix.app.name }}-${{ matrix.vm }}-${{ needs.ci-scope.outputs.e2e-vercel-slot }}
queue: max
permissions:
id-token: write
contents: read
@@ -528,6 +581,10 @@ jobs:
timeout-minutes: 35
needs: ci-scope
if: ${{ needs.ci-scope.outputs.fast-path != 'true' }}
# One cell, so one group per slot. See e2e-vercel-prod's block above.
concurrency:
group: e2e-vercel-multi-region-${{ needs.ci-scope.outputs.e2e-vercel-slot }}
queue: max
permissions:
id-token: write
contents: read
@@ -659,6 +716,12 @@ jobs:
# *asserts* the socket carried the events, since e2e-vercel-prod inherits
# the default but checks nothing.
if: ${{ needs.ci-scope.outputs.fast-path != 'true' }}
# See e2e-vercel-prod's block above. This lane's cells are worth capping
# for a second reason: each one is a full remote `vercel deploy`, so a
# burst of PRs also queues builds on the shared project.
concurrency:
group: e2e-vercel-ws-transport-${{ matrix.app.name }}-${{ needs.ci-scope.outputs.e2e-vercel-slot }}
queue: max
permissions:
id-token: write
contents: read
@@ -856,6 +919,10 @@ jobs:
# Mirrors e2e-vercel-prod's condition exactly, so this lane runs in
# every case that one does, including under `workflow-server-test`.
if: ${{ needs.ci-scope.outputs.fast-path != 'true' }}
# See e2e-vercel-prod and e2e-vercel-ws-transport above.
concurrency:
group: e2e-vercel-http-transport-${{ matrix.app.name }}-${{ needs.ci-scope.outputs.e2e-vercel-slot }}
queue: max
permissions:
id-token: write
contents: read