Skip to content

fix(cold start): serve pages when a service cannot answer - #687

Merged
tkuhn merged 1 commit into
masterfrom
fix/684-cold-start-without-query-service
Sep 4, 2026
Merged

fix(cold start): serve pages when a service cannot answer#687
tkuhn merged 1 commit into
masterfrom
fix/684-cold-start-without-query-service

Conversation

@tkuhn

@tkuhn tkuhn commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Closes #684.

The failure

Reproduced on master with an empty API cache and a query service the library does not admit: the home page hung, /publish answered 500, and the log carried 37 unhandled exceptions — the chain from the issue, UserData.<init>NanodashSession.loadProfileInfo ← page construction, with the error page needing a session of its own and failing the same way.

Behind it was a second one the issue does not name: with the session fixed, HomePage still 500s in MaintainedResourceRepository.findByIdApiCache.retrieveResponseSync. Both repositories already treated a missing response as "nothing yet" but a failing one as fatal.

The fix

  • UserData builds from whatever answered and carries isComplete(). Nothing in it is fatal any more except an unreadable local setting file — a misconfiguration, not an outage.
  • User.install / shouldReplace never let incomplete data replace complete data (a passing outage must not empty out good data); retryIfIncomplete reloads in the background, at most every 30s, never on a request thread (PR Show outdated view results with a spinner instead of a bare spinner (#599) #600's rule).
  • NanodashSession construction cannot fail on profile loading; refreshProfileInfoIfIncomplete(), called from the NanodashPage constructor, reloads the profile once the user data is whole again.
  • ApiCache.retrieveResponseIfAvailable — null instead of throw, for the callers holding state whole pages are built from (SpaceRepository, MaintainedResourceRepository). retrieveResponseSync still throws, so a single view keeps reporting its own failure.
  • ErrorPage renders without its title bar rather than not at all.
  • JustPublishedMessagePanel: the missing-introduction and pending-approval notices are held back while the user data is incomplete — a degraded page must not tell users something false about their own account.

Verification

A second jetty on :37374 against a stub query service answering 503 + Nanopub-Query-Status: LOADING_INITIAL, with NANODASH_API_CACHE_FILE=none — the issue's fresh-deployment scenario — then the stub switched to forwarding to the real service:

master with fix
/ hang / 500 200, showing "The query service is still loading. Lists and status information are incomplete."
/publish, /profile, /error 500 200
after the service came up full home page (templates, events, all view sections), no restart

Recovery was watched end to end in the browser: degraded page → service becomes ready → the instance serves its full home page again.

Tests: 1299 pass (8 new — UserDataResilienceTest, two in ApiCacheTest, one in ErrorPageTest). The error-page test was checked against unfixed code: it fails there with the issue's own "Can't instantiate page" error.

Two notes for the reviewer

  1. Recovery follows the library's clock, not ours. nanopub-java evicts an unhealthy query instance for NANOPUB_QUERY_EVICTION_COOLDOWN_SECONDS (default 300), so the 30-second retry can only succeed once that lapses. It does recover unattended — just up to five minutes after the service is ready.
  2. ServiceHealth.checkQuery can report HEALTHY while dispatch still fails. QueryCall.getApiInstances() returned non-empty the moment the cooldown lapsed while actual calls still threw NotEnoughAPIInstancesException, so the Show a note when no READY registry or query service is available, instead of scattered "API call failed." #681 note can clear before queries work. Left alone here — that is the note's own logic and deserves its own decision.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LqfrG3VXH5tzWXPvYJzXML

An instance with an empty API cache and no query instance the nanopub
library admits could not serve any page at all, its own error page
included. Session construction loads the user data, which needs a query
call, and a failed one threw; Wicket then tried to render the error page,
which needs a session of its own and failed the same way, so the request
ended in an unhandled exception. It takes a cold cache and an unavailable
query service at once -- a fresh deployment whose query service is still
loading, or a fresh container started during an outage -- and it defeats
the service note of #681, since nothing can say a service is unavailable
when no page renders at all.

User data now builds from whatever the services can tell and marks itself
incomplete when one of them cannot answer. Incomplete data is used but
never replaces complete data, and another load is attempted in the
background at most every 30 seconds, so an instance that came up during an
outage comes right on its own. Session construction treats a failed
profile load as an anonymous session rather than as fatal, and loads the
profile again once the user data is complete.

The space and maintained-resource repositories keep their last snapshot
when their query cannot be answered, as they already did for a missing
response: that was the same failure one layer up, where the home page
resolves the home resource. ApiCache.retrieveResponseIfAvailable is the
null-instead-of-throw retrieval they use; retrieveResponseSync still
throws, so a single view keeps reporting its own failure.

The error page falls back to no title bar rather than to no page, since it
is where the other pages' errors end up. The missing-introduction and
pending-approval notices are held back while the user data is incomplete,
so the degraded page does not report on what could not be loaded.

Closes #684

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqfrG3VXH5tzWXPvYJzXML
@tkuhn
tkuhn merged commit c66bc4b into master Sep 4, 2026
8 checks passed
@tkuhn
tkuhn deleted the fix/684-cold-start-without-query-service branch September 4, 2026 15:01
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.

Cold start with no healthy query service 500s on every page, error page included

1 participant