Slow DDP reconnect in Chrome when network status changes

Hi everyone!

When a network status changes (a Wi-Fi network is switched or the laptop goes out of the sleep mode) and you don’t refresh the app page then the next “useSubscribe” takes dozens of seconds sometimes to return true.

The users don’t usually wait for a minute or so seeing the “loading…” status and go refresh the page, and complain that the app is not responding:

const isLoading = useSubscribe('test1');    
const items = useFind(() => Test1.find({}));
if (isLoading()) return <div>loading...</div>;

Questions:

  1. What is a recommended graceful way to handle this delay in UI/client?

  2. Why does this happen in Chrome and works almost instantly in Safari?

Hi,

There are some things you can try. For example:

You can try to gracefully handle this with something like:

const isLoading = useSubscribe('test1');
const [reconnecting, setReconnecting] = useState(false);

useEffect(() => {
  let timeout = setTimeout(() => {
    if (isLoading()) {
      setReconnecting(true);
    }
  }, 5000);

  return () => clearTimeout(timeout);
}, [isLoading]);

const items = useFind(() => Test1.find({}));

if (isLoading()) {
  return reconnecting ? <div>Reconnecting...</div> : <div>loading...</div>;
}

You can use Meteor.status() to check the connection status.

Also, you can try to use Meteor.reconnect() to force the reconnection.

About the difference between Chrome and Safari, it’s hard to say for sure but is likely due to the way Chrome handles reconnections after network interruptions.

In any case, we added this to our backlog and we’re going to investigate this issue further.

As mentioned by @denyhs, a UI element that displays status when disconnected after a set time and allows for manual reconnection controlled by the user is the normal way to solve this (aside from the automated reconnection by Meteor).

We also find that understanding and following the page lifecycle is important. Browsers might have different timeouts on moving from one stage to the next

this package may help you: The trusted source for JavaScript packages, Meteor.js resources and tools | Atmosphere

Appreciate your feedback, thanks!

I have added console logging of Meteor.status every 1 sec.

When Wi-Fi network is switched it takes just few seconds for Meteor client to transition from status = waiting to connected, which is good and expected. The problem is the next useSubscribe (when the client seems to be already in the connected state) sometimes takes 30-60 seconds in Chrome (works instantly in Safari).

If I call Meteor.reconnect while it’s waiting for useSubscribe then it takes ~30 seconds to go to the “connecting” state (see the screenshot attached) and doesn’t help to reduce the wait time either.

Regardless, a user gets an impression of a hung app and needs to fallback on refreshing the browser page (unless they want to wait for a minute for data to appear). Curious why this is not considered as a major flow of connection/DDP mechanism (at least for Chrome) that needs to be fixed somehow.

It is better to create a sample reproduction for those willing to check

[React]

I use a global store (React Context, Redux etc) and have a prop for connected based on the Meteor connection.
I use this prop, wherever I need to cause an effect inside a component but I guess you could depend on it directly.

This is the source code:

…and the Galaxy app:

https://demo-2024-10-10.meteorapp.com/test1

How to reproduce the problem:

  1. Open Test in Chrome.
  2. Switch Wi-Fi network, you’ll see some connection errors in console log.
  3. Wait until “status: connected”.
  4. Click “test2” link on the page.
  5. Monitor how in most cases useSubscribe takes > 1 minute to return data (on the screenshot: 14:35:13 useSubscribe, then “connecting” again, then useSubscribe again x2, then 14:36:15 data loaded).

Looks like when you wait longer (~ 1 min or so) after Meteor reports “connected” (between steps 3 & 4) then the data is returned instantly.

I tested on Chrome in Ubuntu, had different behavior:
After disconnected from network, the status was still connected in about 40 seconds. After that it tried to connect again (waiting, connecting). But after the status was connected, it loaded data instantly.

// Chrome Version 129.0.6668.70 (Official Build) (64-bit)

There is a questionable “connecting” step after the first useSubscribe()

That seems to be the source of the problem.

Have you checked, why?

I don’t think there is anything in the demo app logic that may have triggered the extra “connecting”. My best guess is that the framework can’t get a timely response from the server and initiates reconnection.

Problem #2 is that usually when the laptop wakes up from the sleep mode and you switch to the pre-existing Meteor app Chrome tab you see that the connection is stuck in the “connecting / waiting” mode, until you refresh the page or call Meteor.reconnect.

For problem #2, this is probably a browser page lifecycle issue. Browsers do “kill” tab connections to save resources.

My hunch for #1 is that it is also related to the browser lifecycle but will require debugging.

Our connection bar implementation includes tracking the tab’s visibility and the browser lifecycle to give our users a good experience in various cases (including coming from sleep). We had cases before in which the user kept clicking the connect button, and nothing happened, and they were indeed connected to the internet. The browser lifecycle was the culprit.

We are using this: page-lifecycle - npm

With demo here: https://page-lifecycle.glitch.me/

Those passive, hidden, and frozen states of the browser can kill the UX when not handled correctly

Yeah, looks like this is behavior is very browser-sensitive. We’re seeing issues with Chrome but not with Safari.

Sounds like the best option we have for now is to show the status in UI when disconnected and probably route to a client login view/page when disconnection detected, at least do something that forces user to hit the navigation bar again, because it fixes the situation when you refresh the page.