diff --git a/Makefile b/Makefile index 00181643..b54855d1 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,7 @@ src/kolibri: clean sed -i 's/if name.endswith(".py"):/if name.endswith(".py") or name.endswith(".pyc"):/g' src/kolibri/dist/django/db/migrations/loader.py # Apply kolibri patches patch -d src/ -p1 < patches/0001-server-Set-STATUS_RUNNING-just-once.patch + patch -d src/ -p1 < patches/0002-Fix-Kolibri-blocking-after-starting-with-port-0.patch .PHONY: apps-bundle.zip apps-bundle.zip: diff --git a/patches/0002-Fix-Kolibri-blocking-after-starting-with-port-0.patch b/patches/0002-Fix-Kolibri-blocking-after-starting-with-port-0.patch new file mode 100644 index 00000000..3d2e844d --- /dev/null +++ b/patches/0002-Fix-Kolibri-blocking-after-starting-with-port-0.patch @@ -0,0 +1,157 @@ +From 609b938945f614bd9d086eb33e8652ea697543c6 Mon Sep 17 00:00:00 2001 +From: Dylan McCall +Date: Thu, 7 Apr 2022 11:27:01 -0700 +Subject: [PATCH] Fix Kolibri blocking after starting with port 0 + +After the server starts, BaseServerPlugin waits for it to respond on the +expected port number. For this feature to work, we need it to see the +bind address from the associated httpserver, which is different from the +original bind address when binding to port 0. + +For clarity, we will not initialize the cheroot.wsgi Server with a +bind_addr. Instead, it will be controlled by ServerPlugin itself. + +With this change in place, we can remove PortCache because we no longer +need to reuse port numbers. +--- + kolibri/utils/server.py | 89 ++++++++++------------------------------- + 1 file changed, 21 insertions(+), 68 deletions(-) + +diff --git a/kolibri/utils/server.py b/kolibri/utils/server.py +index d9080d0073..bc582f69ef 100644 +--- a/kolibri/utils/server.py ++++ b/kolibri/utils/server.py +@@ -61,9 +61,6 @@ PID_FILE = os.path.join(conf.KOLIBRI_HOME, "server.pid") + # File used to activate profiling middleware and get profiler PID + PROFILE_LOCK = os.path.join(conf.KOLIBRI_HOME, "server_profile.lock") + +-# File used to store previously available ports +-PORT_CACHE = os.path.join(conf.KOLIBRI_HOME, "port_cache") +- + # File used to send a state transition command to the server process + PROCESS_CONTROL_FLAG = os.path.join(conf.KOLIBRI_HOME, "process_control.flag") + +@@ -129,73 +126,31 @@ def port_is_available_on_host(host, port): + return True + + +-class PortCache: +- def __init__(self): +- self.values = {} +- self.occupied_ports = set() +- self.load() +- +- def lock_port(self, port): +- if port: +- self.occupied_ports.add(port) +- +- def register_port(self, port): +- self.values[port] = True +- self.save() +- +- def get_port(self, host): +- if self.values: +- try: +- port = next( +- p +- for p in self.values +- if not self.values[p] and p not in self.occupied_ports +- ) +- if port: +- if port_is_available_on_host(host, port): +- self.values[port] = True +- return port +- except StopIteration: +- pass +- return None +- +- def save(self): +- with open(PORT_CACHE, "w") as f: +- f.write("\n".join(str(p) for p in self.values)) +- +- def load(self): +- try: +- with open(PORT_CACHE, "r") as f: +- for port in f.readlines(): +- self.values[int(port)] = False +- except IOError: +- pass +- +- +-port_cache = PortCache() +- +- + class ServerPlugin(BaseServerPlugin): +- def subscribe(self): +- super(ServerPlugin, self).subscribe() +- self.bus.subscribe("ENTER", self.ENTER) ++ def __init__(self, *args, **kwargs): ++ httpserver = kwargs.get("httpserver") ++ if not isinstance(httpserver, BaseServer): ++ raise TypeError("httpserver must be a cheroot.wsgi.BaseServer") ++ super(ServerPlugin, self).__init__(*args, **kwargs) ++ self._default_bind_addr = self.bind_addr + +- def unsubscribe(self): +- super(ServerPlugin, self).unsubscribe() +- self.bus.unsubscribe("ENTER", self.ENTER) ++ @property ++ def bind_addr(self): ++ # Instead of using our own copy of bind_addr, mirror httpserver. ++ # This is necessary because methods in BaseServerPlugin expect ++ # bind_addr to match the bind address the server is using, such as ++ # when binding to port 0. ++ return self.httpserver.bind_addr + +- def ENTER(self): +- host, bind_port = self.bind_addr +- if bind_port == 0: +- port = port_cache.get_port(host) +- if port: +- self.bind_addr = (host, port) +- self.httpserver.bind_addr = (host, port) ++ @bind_addr.setter ++ def bind_addr(self, value): ++ self.httpserver.bind_addr = value + + def START(self): ++ # Reset httpserver bind_addr. This value changes if httpserver has ++ # been started before. ++ self.httpserver.bind_addr = self._default_bind_addr + super(ServerPlugin, self).START() +- _, port = self.httpserver.bind_addr +- port_cache.register_port(port) + + @property + def interface(self): +@@ -594,9 +549,7 @@ class KolibriProcessBus(ProcessBus): + self.background = background + self.serve_http = serve_http + self.port = int(port) +- port_cache.lock_port(self.port) + self.zip_port = int(zip_port) +- port_cache.lock_port(self.zip_port) + # On Mac, Python crashes when forking the process, so prevent daemonization until we can figure out + # a better fix. See https://github.com/learningequality/kolibri/issues/4821 + if sys.platform == "darwin": +@@ -724,7 +677,7 @@ class KolibriProcessBus(ProcessBus): + + kolibri_server = KolibriServerPlugin( + self, +- httpserver=Server(kolibri_address, application, **server_config), ++ httpserver=Server(None, application, **server_config), + bind_addr=kolibri_address, + ) + +@@ -735,7 +688,7 @@ class KolibriProcessBus(ProcessBus): + + alt_port_server = ZipContentServerPlugin( + self, +- httpserver=Server(alt_port_addr, alt_application, **server_config), ++ httpserver=Server(None, alt_application, **server_config), + bind_addr=alt_port_addr, + ) + # Subscribe these servers +-- +2.35.1 +