Catastrophic failure of the client-side JavaScript over slow network

Summary
I suspect I am encountering a race condition when bootstrapping my Vaadin Flow app over a throttled network connection, and it results in a catastrophic failure of the client-side JavaScript

Details

  • I have a Vaadin Flow (v24.3.12) app that is using server push (via web sockets)
  • On the server-side I have asynchronous tasks that only get invoked from within onAttach() methods within Components
  • I am careful to ensure that asynchronous tasks are using UI.access() for making changes to UI state
  • Using Chrome Dev Tools I am setting the network throttling to 128kb/s
  • Running without ‘hot deploy’ (i.e. without a dev server) I do a ‘Hard Reload’ of the browser page to force a full bootstrap of the Vaadin client JavaScript
  • I see the following network activity,
    • <various .js resources>
    • generated-flow-imports-DtSjheap.js (6.4MB) gets downloaded
    • FlowClient-BZ2ixoyw.js (233kB) gets downloaded
    • app/?v-r=uidl&v-uidl=1 request is sent (I assume this is the inital request for server-side UI state)
    • push?v-r=push&v-uiId=1&v-pushId=<some hex> (web socket)
  • Looking at the messages being sent over the web-socket, I see a large message that DOES NOT appear without a throttled connection. When it does appear I always have a catastrophic failure in FlowClient-BZ2ixoyw.js (which is minified, so I can’t really tell why)
  • When my app has low volumes of data being displayed the error scenario does not occur
  • I have have been unable to re-produce with an adapted starter-app, but I suspect this is because there is insufficient UI state to make the uidl messages sufficiently large

Thoughts
My best theory is that the inital request for the UI state gets into a race with an async server-side task. If the async task gets to do a server push early enough it will result in complete server-side state being sent over the web-socket, otherwise the initial request ‘wins’ and it will have already sent the server-side state (so web-socket won’t). But I don’t know if this is even plausible.

Questions

  • Can anyone confirm/debunk the possibility of the above theory? It seems suprising that an async task using correct UI locking would attempt to send any UI state before the initial state had already been sent (or at least processed).
  • Is there any event that I can listen for to know when the intial UI state has been sent/processed. In which case I can wait for this event before starting async tasks.
  • Is there a way to run without minified client-side code so that I can see what the error on the client-side is? Not actually sure how useful this will be.
  • Any advice as to how to debug this further or even how to resolve it?

Appreciate any assistance, thank you.

Could you try running your test with a production mode build?

Thanks for the reply.

In short, the issue still ocurrs in production mode.

However, I think I have now identified the issue and a solution …

It seems that when the error ocurrs, the browser sends an rsync message. I have confirmed that a breakpoint here ONLY gets hit under the error scenario. Referring to the network activity listed above, immediately after the push?v-r=push request there is an additional app/?v-r=uidl that has a ‘resynchronize’ paramter set to true. It seems like this rsync request results in the initial request being abandoned, but it does not prevent the push message from being sent. Then the push message (that assumes server-side state) arrives before the rsync response and presumably attempts to update client-side state that is yet to be received from the server, resulting in catastrophic failure.

It turns out there is even a Vaadin property maxMessageSuspendTimeout which determines how long the client will wait before sending an rsync request. Sure enough increasing this value (from the default of 5s) to 30s fixes my issue.

I would still be interested to know if there is a way to be informed on the server-side when the intial UI state has been sent/processed. In which case I would wait for this event before starting async tasks.