hub: resolve the two artifact dropdowns side by side, not one after the other
gates / gates (push) Successful in 30s

Follow-up to the fan-out, and the reason for it is worth recording: THE FIRST FIX DID LESS THAN THE
ARITHMETIC PREDICTED. Concurrency took the page from 26.2s to ~11-18s, not the ~2s expected, so the
gap was chased instead of declared closed.

What it found: the slowest single call on the page is not a per-version sha lookup at all, it is the
PACKAGE SEARCH (/api/v1/packages/admin?type=generic&q=...), measured in-cluster at 1.1-2.2s each
against ~0.24s for a file's metadata. Per-version fan-out cannot touch it — there is one search per
package and they ran in series.

The two dropdowns are independent, so they now resolve side by side, overlapping both searches and
both fan-outs. go test -race clean on the new concurrent paths.

Gitea's latency on this box is load-dependent and varies 2-4x between samples, so the CHANGELOG
quotes a range rather than a single pair of numbers.
This commit is contained in:
2026-08-08 17:50:13 +02:00
parent 5ef7b92b67
commit 9ce8c631d3
2 changed files with 25 additions and 2 deletions
+11
View File
@@ -30,8 +30,19 @@ reads exactly this value to confirm what they are about to vouch, so a stale one
confident wrong answer. The same reasoning `golden_currency_gate.py` already records for the vouched
version. With the fan-out, a cold load needs no cache to be fast.
**And the two dropdowns are resolved side by side**, not one after the other. Each was already
internally concurrent, but in series the page still paid the full latency of BOTH package searches —
and that search is the slowest single call here: measured in-cluster at **1.12.2 s each**, against
~0.24 s for a file's metadata.
**The cap stays at 20** and now bounds the fan-out too, not just the rendered list.
**MEASURED HONESTLY, because the first fix did less than the arithmetic predicted and that mattered.**
Concurrency alone took 26.2 s to ~1118 s, not the ~2 s expected. Chasing the gap is what found the
package SEARCH — `/api/v1/packages/admin?type=generic&q=…` — which no amount of per-version fan-out
touches. Gitea's own latency is load-dependent on this box and varies by 24× between samples, so
single before/after numbers are not trustworthy and a range is quoted instead.
Tests assert the three things that had to survive: order (and that each sha belongs to its own
version), per-version failure isolation, and **the concurrency itself** — a wall-clock assertion plus
an in-flight counter, so a fast run cannot be luck. Red-proof: reverting to the serial loop takes
+14 -2
View File
@@ -906,6 +906,18 @@ func (s *Server) handleConfiguration(w http.ResponseWriter, r *http.Request) {
}
ctx := r.Context()
// The two dropdowns are independent, so they are resolved side by side rather than one after the
// other (v0.100.0). Each is internally concurrent already; doing them in series still cost the
// full latency of BOTH package searches, and that search is the slowest single call on this page
// — measured in-cluster at 1.12.2 s each against ~0.24 s for a file's metadata.
var agentChoices, goldenChoices []artifactChoice
var choicesWG sync.WaitGroup
choicesWG.Add(2)
go func() { defer choicesWG.Done(); agentChoices = s.artifactChoices(ctx, pkgAgent, fileAgent) }()
go func() { defer choicesWG.Done(); goldenChoices = s.artifactChoices(ctx, pkgGolden, fileGolden) }()
choicesWG.Wait()
data := map[string]interface{}{
"CSRFToken": csrfToken,
"CSRFField": s.csrfField(r),
@@ -914,8 +926,8 @@ func (s *Server) handleConfiguration(w http.ResponseWriter, r *http.Request) {
"GlobalFloor": s.store.GetGlobalMinControllerVersion(),
"FloorRes": s.store.ResolveGlobalFloor(),
"Artifacts": s.store.GetArtifactManifest(),
"AgentChoices": s.artifactChoices(ctx, pkgAgent, fileAgent),
"GoldenChoices": s.artifactChoices(ctx, pkgGolden, fileGolden),
"AgentChoices": agentChoices,
"GoldenChoices": goldenChoices,
"Flash": r.URL.Query().Get("flash"),
}
if err := s.templates.ExecuteTemplate(w, "configuration.html", data); err != nil {