Skip to content

fix(ui): stop serving a cold snapshot cache as an empty sites list - #1260

Merged
geodro merged 1 commit into
mainfrom
fix/sites-snapshot-race
Jul 31, 2026
Merged

fix(ui): stop serving a cold snapshot cache as an empty sites list#1260
geodro merged 1 commit into
mainfrom
fix/sites-snapshot-race

Conversation

@geodro

@geodro geodro commented Jul 31, 2026

Copy link
Copy Markdown
Member

The snapshot cache handed a caller that lost the build lock whatever it already held, and on a cold cache that is nil. The handler wrote it out as a zero-byte body, so a browser opening the dashboard just after a restart lost the race on most of the requests it fires at once, and the client's parse failure left the store at the empty array it was initialised with, with nothing scheduled to try again.

A caller holding a usable stale value still returns immediately rather than queuing behind a slow podman inspect, which is what the early return was for. A caller with nothing waits for the build already in flight instead, because answering from a cold cache is making a claim about the registry that nobody checked. All four kinds carried their own copy of that pattern with the same flaw, so they are one type now with the rule stated once.

The second path in the report is fixed the same way. A failure to read the registry is reported rather than turned into an empty slice, a failed build is never cached, and the handler answers 503 instead of a body the dashboard would render as "you have no sites". Where something is already cached, a failed rebuild keeps serving it and leaves the timestamp untouched so the next read retries. Worker health still reports an empty list when its probe fails, since no unhealthy workers is a safe reading of a failed health check in a way that no sites is not.

On the client, loadSites retries at 500ms, 1.5s and 4s while it has never managed to load. Once a list is on screen a later blip leaves it alone.

Verified against the repro in the report, on a machine with twenty sites. Restarting lerd-ui and firing twenty five concurrent reads of /api/sites returned an empty body to twenty four of them beforehand, and the full payload to all twenty five afterwards, holding at forty concurrent across repeated restarts.

Closes #1248.

A caller that lost the build lock was handed whatever the cache already held, which on a cold cache is nil. The handler wrote that out as a zero-byte body, so a browser opening the dashboard just after lerd-ui restarted lost the race on most of the requests it fires at once: twenty five concurrent reads of /api/sites returned an empty body to twenty four of them. On the client that empty body fails to parse and the catch keeps the value the store was initialised with, so the dashboard showed no sites at all and stayed that way until some unrelated mutation published a sites payload over the websocket, which on an idle machine can be minutes.

A caller holding a usable stale value still returns immediately rather than queuing behind a slow podman inspect, which is why the early return existed. A caller with nothing to show now waits for the build in flight, because answering with the cold cache is answering with a claim about the registry that nobody checked. The four kinds each carried their own copy of that pattern, so they are one type now.

From the other direction, buildSites turned a failure to read the registry into an empty slice and the cache stamped it fresh, which would serve one transient failure as the truth for up to five minutes. It reports the error instead, a failed build is never cached, and the handler answers 503 rather than an empty list. Where something is already cached, a failed rebuild keeps serving it and leaves the timestamp alone so the next read tries again. Worker health still reports an empty list when its probe fails, since no unhealthy workers is a safe reading of that, unlike no sites.

Nothing re-fetches the sites list on a timer, so loadSites now retries while it has never managed to load. Once a list is on screen a later failure leaves it there instead of chasing the endpoint.

Closes #1248.
@geodro
geodro requested a review from a team as a code owner July 31, 2026 18:00
@geodro
geodro merged commit f7eee42 into main Jul 31, 2026
3 checks passed
@geodro
geodro deleted the fix/sites-snapshot-race branch July 31, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The dashboard renders an empty sites list when lerd-ui restarts under a cold snapshot cache, and it stays empty until an unrelated event fires

1 participant