From 6d7aa076aafd988e4db4dc8deb0a8afac9018767 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 21 Aug 2026 15:00:08 -0400 Subject: [PATCH] ci: Start Synapse first and let the rest of setup hide its boot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Synapse takes about 67s to answer its healthcheck, and `create realm users` does nothing but wait for it: in a shard log, all twelve realm users queue at 18:34:33 and the first registration request goes out at 18:35:03, a 30s gap that is entirely Synapse coming up. Starting it just before the index import (measured on #5854) hid only the ~24s the import takes, so the gate still waited 38s and setup did not move. Start it as early as the image allows instead — right after Traefik, before the asset download, skills-realm population, the remaining image warm and the import — so roughly the whole boot happens behind work the shard has to do anyway. The warm action grows an `images` input so this can pull just the Synapse image and leave the other four to the existing step; `all` remains the default for every other caller. Expected: `create realm users` falls toward the ~2s the registrations themselves take, without `Wait for realms` growing to absorb it. If the wait does grow, realm-server's own boot is the next wall rather than Synapse's. Co-Authored-By: Claude Opus 5 --- .github/actions/warm-test-images/action.yml | 38 ++++++++++++++++++--- .github/workflows/ci-host.yaml | 31 +++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/.github/actions/warm-test-images/action.yml b/.github/actions/warm-test-images/action.yml index 812828b40d7..c300945308f 100644 --- a/.github/actions/warm-test-images/action.yml +++ b/.github/actions/warm-test-images/action.yml @@ -13,6 +13,12 @@ inputs: registry-prefix: description: GHCR namespace holding the mirrored images. default: ghcr.io/cardstack/boxel + images: + description: >- + Which images to warm: "all", or a space-separated subset of synapse, + smtp4dev, postgres, mock-oauth2-server, caddy. Warming a subset lets a + caller pull the one image it needs next before paying for the rest. + default: all runs: using: composite @@ -23,6 +29,7 @@ runs: GHCR_PREFIX: ${{ inputs.registry-prefix }} GHCR_TOKEN: ${{ github.token }} GHCR_USER: ${{ github.actor }} + IMAGES: ${{ inputs.images }} run: | set -uo pipefail # Authenticate so the private mirror packages are pullable. Best-effort: @@ -64,13 +71,34 @@ runs: pids+=($!) } + # `all` (the default) keeps every caller that doesn't care unchanged. + wanted() { + if [ "$IMAGES" = "all" ]; then + return 0 + fi + case " $IMAGES " in + *" $1 "*) return 0 ;; + *) return 1 ;; + esac + } + logs=() pids=() - warm_bg matrixdotorg/synapse:v1.126.0 synapse:v1.126.0 - warm_bg rnwood/smtp4dev:v3.1 smtp4dev:v3.1 - warm_bg postgres:16.3 postgres:16.3 - warm_bg ghcr.io/navikt/mock-oauth2-server:4.0.1 mock-oauth2-server:4.0.1 - warm_bg caddy:2.10.2-alpine caddy:2.10.2-alpine + if wanted synapse; then + warm_bg matrixdotorg/synapse:v1.126.0 synapse:v1.126.0 + fi + if wanted smtp4dev; then + warm_bg rnwood/smtp4dev:v3.1 smtp4dev:v3.1 + fi + if wanted postgres; then + warm_bg postgres:16.3 postgres:16.3 + fi + if wanted mock-oauth2-server; then + warm_bg ghcr.io/navikt/mock-oauth2-server:4.0.1 mock-oauth2-server:4.0.1 + fi + if wanted caddy; then + warm_bg caddy:2.10.2-alpine caddy:2.10.2-alpine + fi # `warm` never fails (it downgrades a miss to a warning), so nothing # here needs to inspect exit status — but wait on each pid rather than diff --git a/.github/workflows/ci-host.yaml b/.github/workflows/ci-host.yaml index 3ee46ca3cc2..80289eee0ae 100644 --- a/.github/workflows/ci-host.yaml +++ b/.github/workflows/ci-host.yaml @@ -560,6 +560,34 @@ jobs: # downloaded here, avoiding a per-shard rebuild. The artifact # bundles boxel-icons/dist, boxel-ui/dist, packages/host/dist, # and the build manifest. + # Synapse takes about 67s to answer its healthcheck, and until it does, + # `create realm users` below just waits. Starting it after the index + # import only hides ~24s of that; starting it here hides it behind the + # asset download, the skills-realm population, the rest of the image + # warm and the import together — roughly the whole boot. + # + # Only the synapse image is pulled here so this step stays short; the + # step further down warms the other four. + - name: Warm the Synapse image ahead of the rest + continue-on-error: true + uses: ./.github/actions/warm-test-images + with: + images: synapse + + # Backgrounded because `assert-synapse-running` blocks on Synapse's own + # healthcheck. Idempotent: when `test-services:host` reaches + # `start:matrix` it finds the container up and only re-registers the + # Traefik route, and the container exists within seconds of this step, + # so the two cannot race to create it. Nothing waits on this — + # `create realm users` already polls and reports its own failure. + # Traefik is up by this point, which Synapse needs in environment mode + # to publish its dynamically chosen host port. + - name: Start Synapse + working-directory: packages/matrix + run: | + pnpm assert-synapse-running >> /tmp/server.log 2>&1 & + echo "Synapse is starting in the background; output goes to /tmp/server.log" + - name: Download test web assets uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -595,6 +623,9 @@ jobs: - name: Warm test Docker images from the GHCR mirror continue-on-error: true uses: ./.github/actions/warm-test-images + with: + # Synapse was warmed and started earlier, above. + images: smtp4dev postgres mock-oauth2-server caddy # Seed the index tables from a recent main boxel-index-cache artifact, # leaving the boot index to reconcile whatever this checkout changed # rather than indexing every realm from scratch. Runs before the