Fix infinite route-loader spinner from per-navigation root cache inva… - #5936
Fix infinite route-loader spinner from per-navigation root cache inva…#5936naomigassler wants to merge 1 commit into
Conversation
…lidation BrowserInitService invalidated the root /server/api endpoint cache on every NavigationStart. That marks the root request stale (RootDataService .invalidateRootCache -> RequestService.setStaleByHref). HALEndpointService .getEndpointMapAt, used by getEndpoint() for every data request, discards a stale root via filter(rd => !rd.isStale), and its re-fetch can lose the race with the next invalidation, so getEndpoint() never resolves, the route resolver never completes, and the route loader spins forever. The root endpoint map is static between navigations, so invalidating it on every navigation is unnecessary. Remove the per-NavigationStart invalidation and keep the one-time invalidation at app init; backend availability is still established there and surfaces through normal request failures. Fixes DSpace#3584, DSpace#3697.
|
I was able to reproduce the issue with the DSpace sample data. The steps to reproduce it locally were:
I tested this PR, and the issue did not occur anymore 🎉. But then again, I tried to reproduce the original issue on main, just to double check - and could not successfully reproduce it anymore. So I would suggest:
Thanks for the PR! This is a great contribution in my opinion. |
|
Hey @tinsch and others in here. Starting point: I see the infinite route loader appear when I double-click very quickly on a navigation item.
Screencast on the DSpace sandbox page The issue can apparently be reproduced this way—unless I’m missing something. If I now apply the fix from the merge request, the error no longer occurs. Therefore, in my opinion, the fix makes perfect sense—especially since the logic in the underlying code apparently isn’t needed at all. So far, so good. But now I’m wondering if this really solves the problem. Even if we probably don’t actually need to manually invalidate the So, another approach or at least a try could be to swap the order of the following two lines: dspace-angular/src/app/core/data/base/base-data.service.ts Lines 312 to 314 in a52ba24 We could move the
No matter what sequence of clicks I used after making this change, I could no longer trigger the state where the loading spinner is displayed continuously. Long story short:
I’d be very interested in hearing your thoughts on these points, and of course whether you can reproduce the behavior. |
|
After further investigation, I might have found a better, more logic solution, which leaves my previous idea seeming obsolete. Original code: dspace-angular/src/app/core/data/request.service.ts Lines 155 to 176 in a52ba24 My (partial) code changes: This change targets to check Another, additional check I added in the Original code: dspace-angular/src/app/core/data/request.service.ts Lines 431 to 436 in a52ba24 My (partial) code changes: This change ensures that a request entry that exists in the request cache but is stale or expired is always re-fetched from the server—regardless of whether the associated object is still present in the object cache. Previously, in this case, the system would incorrectly fall back on the object cache and suppress the request, even though the data was marked as stale. As before, the fix in this merge request makes perfect sense. My additional changes could make the app even more robust. I have to do some more testing about these changes (especially in our customized instance)... but wanted to let you know about my current investigation. |
|
Thanks @jlipka for your investigation. I can reproduce the spinner issue on sandbox with your click sequence, but not locally. I tried my click sequence again to reproduce the issue, and it happened reliably on main and was fixed on the PR branch. However, I looked into the PR code and tested it further: The problem is, we now don't invalidate the backend route anymore on NavigationStart. This means that if the backend is down (simulated locally by turning off the backend container) the frontend won't notice. I can still click around in the UI and get some errors on certain pages, but this will be confusing for end users who don't know what's happening when the UI still looks somewhat fine. It can lead to data loss on the users side, and a lot of questions to the repository owners should the backend be down. Unfortunately I think we cannot use the fix for this reason. @naomigassler could you look into this please? Maybe there is a way to prevent the problem I described and still fix the spinner issues by tweaking the code a bit. To help with further investigations: The route was |
|
We measured every candidate on a test box against two scenarios: A the
Measured on our DSpace 9.3 deployment with added REST latency, not on A correction to our own issue report. The alternative we suggested there — On the objection. "Won't notice the backend is down" isn't quite what we So the concern was sound even where the mechanism wasn't, and chasing it turned up But on unmodified What the actual decision is. Today's
They are compatible. Happy to open the probe separately if there is appetite. On the Caveat. Our reproduction amplifies the production race with added REST latency, |


PR: Fix infinite route-loader spinner on navigation (stale root-endpoint cache deadlock)
Target branch:
DSpace/dspace-angular:mainRelates to: #3584, #3697
Fixes #5855
Description
BrowserInitServiceinvalidates the root API endpoint cache on everyNavigationStart. On a subsequent request,HALEndpointService.getEndpointMapAt(hit on essentially every request via
getEndpoint()) discards the now-staleroot
/server/apientry and triggers a re-fetch that can deadlock — theroute resolver never completes,
NavigationEndnever fires, and theds-base-rootroute-loader spinner hangs indefinitely on a frozen store.The root endpoint map is effectively static between navigations, so
re-invalidating it on each
NavigationStartis unnecessary. Backend-downdetection still happens at init and through normal request-failure handling.
Root cause
BrowserInitService→invalidateRootCache()on everyNavigationStart./server/apiendpoint cache stale.getEndpoint()→getEndpointMapAtdiscards the stale root → re-fetchdeadlocks → resolver never resolves →
NavigationEndnever fires → spinnerhangs forever.
intermittent).
Steps to reproduce
Communities & Collections).
Reproducible on a small instance (~7k items) — not load- or scale-dependent.
Higher REST/proxy latency increases the frequency.
Fix
Remove the per-
NavigationStartinvalidateRootCache()call inbrowser-init.service.ts; keep the one-time invalidation at init. No behaviorchange to backend-down detection.
How to test
always resolves.
app still detects/handles it (init-time + request-failure paths).
Tests
Added a spec asserting the root endpoint cache is invalidated once at init
and not on subsequent
NavigationStartevents.Checklist notes
yarn lintandyarn check-circ-deps.