Intermittent Endless Loading in Vaadin View with Async Data

Hi all,

I’m experiencing an intermittent “endless loading” issue in my Vaadin (Flow) view (ScenarioDetailsView). The main progress bar sometimes never disappears, and the content doesn’t load.

  • This happens most often right after server startup.
  • A temporary workaround is to navigate back, open a different scenario (which usually loads quickly), and then try the original scenario again – it might then load correctly. The issue can still reappear later.

View Logic Overview:

My ScenarioDetailsView is a Composite<VerticalLayout> implementing HasUrlParameter and BeforeEnterObserver.

  1. setParameter(id): Validates and stores the scenario ID from the URL.
  2. beforeEnter(): Calls loadScenarioData().
  3. loadScenarioData():
    • Clears previous content and shows a global ProgressBar.
    • Submits a main loading task to an ExecutorService (created with Executors.newVirtualThreadPerTaskExecutor()).
    • This task uses CompletableFuture.supplyAsync() to fetch the main Scenario object (with a 15s timeout).
    • Upon successful fetch, it uses ui.access() to:
      • Hide the global ProgressBar.
      • Build the UI: a header, scenario metadata, and an Accordion.
      • Nested Async for Accordion Panels: For each AccordionPanel (e.g., “General Info”, “Patient State”), a new asynchronous task is submitted to the same executorService. This task fetches panel-specific data and then updates the panel’s content, again using ui.access(). Each panel initially shows its own loading placeholder.
  4. onDetach(): Shuts down the executorService (shutdownNow() and awaitTermination()).
  5. Backup Timeout: loadScenarioData() also starts a 20-second backup timer. If the main loading isn’t complete by then, it tries to show an error and navigate away.

Code Snippet (Pattern for Accordion Panel Loading):
This pattern is used for several accordion panels, all triggered after the main scenario data is loaded and the basic UI structure is built within the first ui.access() call.

// Inside addAccordionToContent, after main scenario object is confirmed to be loaded
private void createAndAddGeneralInfoPanel(Accordion accordion, UI ui) {
    AccordionPanel panel = accordion.add("General Info", createLoadingPlaceholder("Loading..."));
    // 'scenario' (main object) is available here
    loadGeneralSupportDataAsync(ui, panel, scenario, ...); // Starts another async task
}

private void loadGeneralSupportDataAsync(UI ui, AccordionPanel panelToUpdate, Scenario currentScenario, ...) {
    executorService.submit(() -> {
        try {
            // ... Fetch data for this panel (e.g., String details = someService.getDetails(currentScenario.getId())) ...
            if (detached.get() || ui.isClosing()) return;

            ui.access(() -> {
                // ... Update panelToUpdate.setContent(detailsComponent) ...
            });
        } catch (Exception e_task) {
            // ... Log error and update panel with error message via ui.access() ...
        }
    });
}

Questions:

  1. What are common causes for this kind of intermittent endless loading, especially on initial server startup, given the nested asynchronous operations and ui.access() calls?
  2. Could there be race conditions or issues with the ExecutorService (especially with virtual threads) or UI/session readiness that lead to the ui.access() callbacks not firing or an underlying task getting stuck?
  3. Any suggestions for debugging this effectively? The issue is hard to reproduce consistently.

Thanks for any insights!

You have to find out, what the “blue bar” is telling you first. The
most likely problems are:

  • a client problem (very unlikely, if you don’t run your own client
    code). Check the browsers dev-console for errors
  • although it’s a client error it might result from a server error;
    check your server log
  • it might be a legit deadlock on the server (multiple things attempting
    to get the exclusive lock on your session); you will see an legit
    connection in your browsers network-tab waiting for a response.

Make sure, your server-side logging is intact (e.g. throw an exception
somewhere just to be sure you will have proper logging).

If everything points to a deadlock, then add logging to get a better
picture of what is happening. E.g. is your asynchronous code “falling
through” into the access parts; as in, is the access block the one
waiting for a promise to be delivered etc.

Also if it’s hard to debug, concurrency usually is. Race conditions you
can only ever be sure, that they are happening, when they are happening
– never if they don’t. So why would it only fail the first time? Are
there caches involved? Can you disable those to make the problem
trigger more regularly?

1 Like