Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
157 changes: 157 additions & 0 deletions patches/0002-Fix-Kolibri-blocking-after-starting-with-port-0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
From 609b938945f614bd9d086eb33e8652ea697543c6 Mon Sep 17 00:00:00 2001
From: Dylan McCall <dylan@endlessos.org>
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