Repository: learningequality/kolibri

Issue:
Windows app won't start up again if there's a leftover server.pid file
https://www.notion.so/learningequality/BUG-Windows-app-won-t-start-up-again-if-there-s-a-leftover-server-pid-file-b11d3d7450c14844bda03ee81f06f373

There is another Kolibri server running. Please use `kolibri stop` and try again.

Even though the PID in the [server.pid](http://server.pid) doesn't exist anymore:

```
42412
8080
0
4
```

UPDATE: it seems that `_windows_pid_exists` is still returning `True` for the PID even though it no longer exists.


Discussion:

Resolving patch:
diff --git a/kolibri/utils/server.py b/kolibri/utils/server.py
--- a/kolibri/utils/server.py
+++ b/kolibri/utils/server.py
@@ -116,7 +116,7 @@ def error_log(self, msg="", level=20, traceback=False):
         return logger.log(level, msg)
 
 
-def check_port_availability(host, port):
+def port_is_available_on_host(host, port):
     """
     Make sure the port is available for the server to start.
     """
@@ -152,7 +152,7 @@ def get_port(self, host):
                     if not self.values[p] and p not in self.occupied_ports
                 )
                 if port:
-                    if check_port_availability(host, port):
+                    if port_is_available_on_host(host, port):
                         self.values[port] = True
                         return port
             except StopIteration:
@@ -603,11 +603,10 @@ def __init__(
         if sys.platform == "darwin":
             self.background = False
 
-        # Check if there are other kolibri instances running
-        # If there are, then we need to stop users from starting kolibri again.
-        pid, _, _, status = _read_pid_file(self.pid_file)
-
-        if status in IS_RUNNING and pid_exists(pid):
+        if (
+            self._kolibri_appears_to_be_running()
+            and self._kolibri_main_port_is_occupied()
+        ):
             logger.error(
                 "There is another Kolibri server running. "
                 "Please use `kolibri stop` and try again."
@@ -662,6 +661,17 @@ def __init__(
         reload_plugin = ProcessControlPlugin(self)
         reload_plugin.subscribe()
 
+    def _kolibri_appears_to_be_running(self):
+        # Check if there are other kolibri instances running
+        # If there are, then we need to stop users from starting kolibri again.
+        pid, _, _, status = _read_pid_file(self.pid_file)
+        return status in IS_RUNNING and pid_exists(pid)
+
+    def _kolibri_main_port_is_occupied(self):
+        if not self.serve_http:
+            return False
+        return not port_is_available_on_host(self.listen_address, self.port)
+
     def _port_check(self, port):
         # In case that something other than Kolibri occupies the port,
         # check the port's availability.
@@ -672,7 +682,7 @@ def _port_check(self, port):
         if (
             not os.environ.get("LISTEN_PID", None)
             and port
-            and not check_port_availability(self.listen_address, port)
+            and not port_is_available_on_host(self.listen_address, port)
         ):
             # Port is occupied
             logger.error(


Test patch:
diff --git a/kolibri/utils/tests/test_server.py b/kolibri/utils/tests/test_server.py
--- a/kolibri/utils/tests/test_server.py
+++ b/kolibri/utils/tests/test_server.py
@@ -288,11 +288,15 @@ def test_unclean_shutdown(self, pid_exists_mock, read_pid_file_mock):
             run_mock.assert_called()
 
     @mock.patch("kolibri.utils.server.pid_exists")
-    def test_server_running(self, pid_exists_mock, read_pid_file_mock):
+    @mock.patch("kolibri.utils.server.wait_for_free_port")
+    def test_server_running(
+        self, wait_for_port_mock, pid_exists_mock, read_pid_file_mock
+    ):
+        wait_for_port_mock.side_effect = OSError
         pid_exists_mock.return_value = True
         read_pid_file_mock.return_value = (1000, 8000, 8001, server.STATUS_RUNNING)
         with self.assertRaises(SystemExit):
-            server.start()
+            server.start(port=8000)
 
 
 class ServerSignalHandlerTestCase(TestCase):