App constantly refreshing after an update

Unfortunately - this didn’t work for me. I think disabling caching in Nginx is working but I can’t full test it until the previous 30d cache expires

I can confirm that

if ($uri != '/') {
    expires 30d;
}

caused infinite refreshes for some users of my app recently.

As posted above. This is helpful:


I seemed to have missed it when it was first posted.

I am facing the endless reload problem too but it is a fresh deployment, not an update. First I had the problem when adding the nginx proxy on my development machine, and fixed it by removing the caching block. Now I am using the exact same nginx config/version and it keeps reloading.

It seems to not be triggered by the caching as even with chrome’s console open with ‘Disable cache’ on it keeps refreshing (while it used to stop it on the development machine)

I do not know where to turn now as I don’t see any error anywhere… I thought that maybe it could be a websocket problem or something but the only thing that changes between development and production is that the application is bundled.

Do you have ssl setup? Force ssl package can cause issues like this.

Also the app cache package can cause this issue.

Try running it without nginx and see if you have the issue.

I’m on Meteor 1.3.5.1 and for a long time I’ve ran my production deployment with just MUP without issues. I had a requirement to add SSL to my site so over the weekend I added nginx and SSL (never had Force SSL installed). The app almost immediately starting reloading constantly. I read the issue was solved by removing caching from the config file.

if ($uri != '/') {
  expires 30d;
}

That worked. But I noticed while debugging another issue, at least one of my tracker methods that uses Meteor.userId() and a few Session variables is constantly triggering when I don’t think it should (I never noticed this before). Is there a way to find out, when one has a few Session variables inside a Tracker method, what exactly is triggering it to run?

I have not setup SSL yet.

There is no meteor package named app cache or something similar on my application.

I’ve tried running the application directly by exposing its port and the result is the same.

@aadams I had removed that part already on a previous installation, never had those lines on this install.

Actually, I feel a bit dumb… We just found out that meteor-cluster’s balancer url was hardcoded instead of using the environment URL… I don’t understand why this package would want to keep refreshing like that though, but setting the correct URL fixed the problem for us.

I experienced this issue with my new nginx config.
This thread already mentions that caching is the problem, but here’s a bit deeper explanation of this problem:

The HTML generated by Meteor contains a bit of configuration like:

__meteor_runtime_config__ = JSON.parse(decodeURIComponent("%7B%22meteorRelease%22%3A%22METEOR%401.4.2.3%22%2C%22meteorEnv%22%3A%7B%22NODE_ENV%22%3A%22development%22%2C%22TEST_METADATA%22%3A%22%7B%7D%22%7D%2C%22PUBLIC_SETTINGS%22%3A%7B%7D%2C%22ROOT_URL%22%3A%22https%3A%2F%2Fdev.barco.com%22%2C%22ROOT_URL_PATH_PREFIX%22%3A%22%22%2C%22appId%22%3A%221unoj8hnpdlfkilwye6%22%2C%22accountsConfigCalled%22%3Atrue%2C%22autoupdateVersion%22%3A%221f075a5d589e6b6752ec729586668b1fd79c0e4c%22%2C%22autoupdateVersionRefreshable%22%3A%22d4603c0cf7592ed19eabd2b57718ab72ab3a81cf%22%2C%22autoupdateVersionCordova%22%3A%22none%22%7D"));

Parsed this contains the current version of the client-bundle version (autoUpdateVersion).
The autoupdate package has a subscription to a publication that publishes new version hashes. If the hash it receives doesn’t match with the one in autoUpdateVersion, the autoupdate package asks the reload package to reload the page.

If the html has cache-headers, it will be loaded again from disk, so with the old configuration data (and therefore version info) and it will instantly reload.

Therefore removing cache-headers from your nginx like:

if ($uri != '/') {
    expires 30d;
}

Helps, but it throws the baby out with the bathwater.
@abernix Couldn’t Meteor set the ETag on this html to the autoupdate version?

2 Likes

An ETag could be added, yes, and I’d be willing to review a PR for it, but there’s a caveat.

While the ETag on the HTML “boilerplate” (as it’s referred to) could potentially avoid you having to make an exception like you’ve done, it’s not fair to say that it “throws the baby out with the bathwater” because ultimately the resources that Nginx would cache based on the ETag header still need to be verified on every request with the Meteor app (via a If-Modified-Since header on a GET request) to make sure they haven’t changed (for example, if you’ve updated your app). Meteor would still have to generate the boilerplate to determine if the content was different and respond with a 304 Not Modified response code if it was the same.

Granted, you could force it to only revalidate every so often, you are not really going to see a cache benefit from this process as Meteor is very, very quick at serving the boilerplate, with upwards of 1200 requests per second served in less than 100ms (these numbers are based on testing 5000 requests at a concurrency of 15). Additionally, considering the fact that Meteor supports features such as dynamicHead, those variables would have to be considered as well as it would affect the ETag value.

Ultimately, I think using an ETag on the boilerplate might break more than it would solve.

Yes, you’d still get a request on the server, but it would prevent Meteor having to send the boilerplate to the client.
Furthermore, you could let nginx cache the boilerplate, at least with proxy_cache_revalidate on, which would still have a lot more throughput. But I didn’t know the performance metrix you mentioned, so maybe the gains are not as big as I intuitively thought.

Yes, but my point was that by taking any route other than revalidating on every single request this same reload pattern could be exhibited by adding the ETag. And since the revalidation request would still yield a rendering of the boilerplate in Meteor (due to the dynamic aspects I referenced previously), the net gain would be near-zero or zero.

Ultimately, if caching is done incorrectly, problems will arise. :slight_smile:

I do think it was a very fair consideration though!

We have seen infinite-reloads problem pretty consistently, with no relief from any of the commonly-suggested fixes.

We have been using a server-side heuristic that recognizes clients in a reload loop and 404’s the appcache manifest. This works, but it takes several reloads in a row for a client to trigger the heuristic, and then only on the next subsequent reload is the actual appcache restored. So users experience a limited amount of looping on every code update, and would not be able to use the app offline between the 404’s load and whenever they next load (and finally get the updated appcache).

pauldulong’s observation above didn’t work for us but did inspire us to a related solution that does. Simply returning false from onMigration appeared to cause clients with an appcache established to never load updates, even during reloads. However, given the knowledge that onMigration false could at least stop the spurious reloads, and doing some testing we saw Meteor triggers a reload the moment the appcache is discovered out of date, and keeps triggering reloads so downloading can never complete. If you monitor appcache fields and hook onMigration you can get reload working as intended, though.

Below is what we’re now using, eliding our app-specific stuff like UI updates. With this code we see reliable single-reload appcache updates and can control the timing of the updates within our app’s UI.

if (Meteor.isClient) {
    var migrationstate = 0;  // 0==none, 2==appcache update in progress

    // Monitor appcache downloading state to trigger a reload
    // once the download is complete
    var appcache_downloading = false;
    if (window.applicationCache) {
        window.applicationCache.addEventListener('downloading',function() {
            if (appcache_downloading) return;
            if (window.applicationCache.status !== window.applicationCache.DOWNLOADING) return;
            appcache_downloading = true;
            console.log("mig: appcache downloading started");
            window.applicationCache.addEventListener('updateready', function() {
                console.log("mig: appcache ready for restart");
                location.reload();
            });
        });
    }

    // Hook Meteor's onMigrate to detect the out-of-date condition, trigger an app-cache
    // update, and prevent Meteor from prematurely reloading
    Meteor._reload.onMigrate( "useractivity", function(retry) {
        console.log("mig: got onMigrate event indicating new software is ready", migrationstate);
        if (migrationstate>0) return false;  
        if (window.applicationCache.status !== window.applicationCache.DOWNLOADING) return false;

        if (window.applicationCache && (window.applicationCache.status === 1)) {
            window.applicationCache.update();
            migrationstate = 2;
            console.log("mig: triggered appcache to check for and download update");
            return false;
        } else {
            console.log("mig: allowed hotpatch of update given no appcache");
            return [true];
        }
    });
}

1 Like

We also hit this roadblock and moved to ServiceWorkers instead. Much nicer and more granular, but alas, not supported on iOS yet (we are using an App for mobile, so it’s not an issue for us).

The problem is that the reload does nothing more than refresh the current page, which comes from the AppCache, so you keep going in circles. We tried a similar solution to yours but it was too lengthy to debug and test. But the essence is the same, you have to do a full reload of the page, not just a replace like in here

However, I am surprised you didn’t have to modify the AppCache package to not cache / (first line in the caching sections). Are you using stock AppCache package from MDG?

I had this issue before and for me it was related to having the reactive-var package and setting NGINX listen [::]:80 ipv6only=on;.

FWIW, this solution worked for me

add it to client/main.js

Meteor._reload.onMigrate(function () { return [false]; });`

That fixed my problem in my own browser. I pasted that function and manually restarted and it worked. But I’m afraid of other people’s browser. =/

I had the same problem.
My app is deployed via mup as two instances on the same server with nginx load balancing.
I have figured out that autoupdateVersion of meteor apps instances are different. That’s the cause of the infinite reloading. But instances are the same, at least should be, since autoupdateVersion is a hash of the app code. But they weren’t.

Cause: On deploy, mup.js file was dynamically created in the root of the app for instances with different port and it was counted as part of the app. That’s why autoupdateVersion was different.

Solution: Do mup deploy from a directory which are not loaded as part of your app code. For example, i’ve created .deploy directory.

I suppose some people had the same scenario.

So I also ran into this problem today and turns out … I was a bit of an idiot! :smiley:
What happened was:

I have some tasks (“cronjobs”) running server side. But of course I want these tasks only to run on one of my app (docker) instances.
So what I did is put a server attr into my Meteor.settings.public --> BAD IDEA! Due to that, the Meteor.settings were obviously different from each other --> different meteor.js hash --> weird reloads occuring.

So if you deploy your app multiple times with a nginx proxy in front of it: be sure to upload two exactly equal app packages!

sidenote: I solved my server tasks problem by using a custom process.env var to distinguish the two instances.

This plaged me for a day on my dev and staging server. I thought I’d fixed it several times but what seems to have ultimately fixed the issue for me was removing this line:

res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for a year

From the function contained here:

WebApp.rawConnectHandlers.use(function(req, res, next) {
		res.setHeader('Access-Control-Allow-Origin', Config.base_url.href.slice(0, -1));	// Remove trailing slash.

		//
		// IS SETTING Cache-Control CAUSING THE CONSTANT REFRESH?
		// See https://forums.meteor.com/t/app-constantly-refreshing-after-an-update/23586/86
		//
		//res.setHeader('Cache-Control', 'public, max-age=31536000');	// Cache for a year

		next();
	});

It’s been a week or so now and I haven’t seen it once, whereas it was happening constantly on that fateful day.

Remove expires from your nginx file.

Elie