Repository: mkdocs/mkdocs

Issue:
`mkdocs serve --livereload` hangs, forces to CTRL-C and restart
Whenever we use `mkdocs serve --livereload`, the livereload server blocks, as MkDocs waits for changes to files that can be reported back to the browser. This technique is known as long polling, and albeit we now have wide support for websockets which are a superior solution, it's a perfectly fine implementation for livereload functionality.

Blocking is implemented here:

https://github.com/mkdocs/mkdocs/blob/347c3a91dd3e82937ce2b4783c60abf2477ff844/mkdocs/livereload/__init__.py#L262

However, sometimes when I navigate the site while working on it, MkDocs gets stuck in this condition and waits for changes to files. There are three ways to unblock it:

1. Change and save a file, so MkDocs rebuilds it and returns it to the browser
2. Press <kbd>ESC</kbd> in the browser to cancel the `/livereload` request
3. Wait for a minute until it times out

I'm not very familiar with the livereload implementation, but the problem seems to be that the server is occupied with waiting for the livereload request to finish hitting one of the conditions mentioned above, which prohibits the navigation from happening – see the loading indicator after navigating for the third time:

https://github.com/mkdocs/mkdocs/assets/932156/04869d26-3e17-4cee-b7e5-0917c95e150d

My testing shows that this problem can be solved by cancelling the pending `/livereload` request before navigation.
Abort livereload request before navigation
Closes #3389. 

__Edit__: superseded by #3391.


Discussion:

Resolving patch:
diff --git a/mkdocs/livereload/__init__.py b/mkdocs/livereload/__init__.py
--- a/mkdocs/livereload/__init__.py
+++ b/mkdocs/livereload/__init__.py
@@ -28,24 +28,44 @@
 
 _SCRIPT_TEMPLATE_STR = """
 var livereload = function(epoch, requestId) {
-    var req = new XMLHttpRequest();
-    req.onloadend = function() {
-        window.removeEventListener("beforeunload", abort);
-        if (parseFloat(this.responseText) > epoch) {
-            location.reload();
-            return;
+    var req, timeout;
+
+    var poll = function() {
+        req = new XMLHttpRequest();
+        req.onloadend = function() {
+            if (parseFloat(this.responseText) > epoch) {
+                location.reload();
+            } else {
+                timeout = setTimeout(poll, this.status === 200 ? 0 : 3000);
+            }
+        };
+        req.open("GET", "/livereload/" + epoch + "/" + requestId);
+        req.send();
+    }
+
+    var stop = function() {
+        if (req) {
+            req.abort();
         }
-        var launchNext = livereload.bind(this, epoch, requestId);
-        if (this.status === 200) {
-            launchNext();
-        } else {
-            setTimeout(launchNext, 3000);
+        if (timeout) {
+            clearTimeout(timeout);
         }
+        req = timeout = undefined;
     };
-    var abort = req.abort.bind(req);
-    window.addEventListener("beforeunload", abort);
-    req.open("GET", "/livereload/" + epoch + "/" + requestId);
-    req.send();
+
+    window.addEventListener("load", function() {
+        if (document.visibilityState === "visible") {
+            poll();
+        }
+    });
+    window.addEventListener("visibilitychange", function() {
+        if (document.visibilityState === "visible") {
+            poll();
+        } else {
+            stop();
+        }
+    });
+    window.addEventListener("beforeunload", stop);
 
     console.log('Enabled live reload');
 }


Test patch: