From 939535182eb5675c8ba5a5a55bdbdce16fa73308 Mon Sep 17 00:00:00 2001 From: Alexander Druz Date: Tue, 11 Jul 2023 15:10:04 +0200 Subject: [PATCH 1/7] Pass opened socket as FD to Uvicorn --- renumics/spotlight/server.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/renumics/spotlight/server.py b/renumics/spotlight/server.py index e483fd5e..43209cbc 100644 --- a/renumics/spotlight/server.py +++ b/renumics/spotlight/server.py @@ -35,6 +35,7 @@ class Server: _host: str _port: int _requested_port: int + _sock: Optional[socket.socket] _vite: Optional[Vite] @@ -63,6 +64,7 @@ def __init__(self, host: str = "127.0.0.1", port: int = 8000) -> None: self._host = host self._requested_port = port self._port = self._requested_port + self._sock = None self.process = None self.connected_frontends = 0 @@ -118,12 +120,10 @@ def start(self, config: AppConfig) -> None: self._vite.start() env["VITE_URL"] = self._vite.url - # automatic port selection - if self._requested_port == 0: - with socket.socket() as sock: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind((self._host, self._port)) - self._port = sock.getsockname()[1] + self._sock = socket.socket() + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._sock.bind((self._host, self._port)) + self._port = self._sock.getsockname()[1] command = [ sys.executable, @@ -132,8 +132,8 @@ def start(self, config: AppConfig) -> None: "renumics.spotlight.app:SpotlightApp", "--host", self._host, - "--port", - str(self._port), + "--fd", + str(self._sock.fileno()), "--log-level", "critical", "--http", @@ -150,7 +150,9 @@ def start(self, config: AppConfig) -> None: # start uvicorn # pylint: disable=consider-using-with - self.process = subprocess.Popen(command, env=env) + self.process = subprocess.Popen( + command, env=env, pass_fds=(self._sock.fileno(),) + ) self._startup_complete_event.wait(timeout=120) From 646db761f570eee979bed0bd8f39215221e7d7f7 Mon Sep 17 00:00:00 2001 From: Alexander Druz Date: Tue, 11 Jul 2023 15:11:42 +0200 Subject: [PATCH 2/7] Temporarily actvate win and macos install test on dev branch --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1646201b..2789603d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,7 +120,7 @@ jobs: uses: druzsan/setup-matrix@v1 with: matrix: | - os: ubuntu-latest, + os: ubuntu-latest windows-latest macos-latest, python-version: 3.8 - name: Print matrix run: echo "$MATRIX" | yq -P '{"matrix":.}' From 9168cf62f941b8f3bbe2c6d6b18861ae5e9de85d Mon Sep 17 00:00:00 2001 From: Alexander Druz Date: Tue, 11 Jul 2023 15:41:52 +0200 Subject: [PATCH 3/7] Do not pass socket FD on Windows, but do not close all FDs on Windows --- renumics/spotlight/server.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/renumics/spotlight/server.py b/renumics/spotlight/server.py index 43209cbc..3329c123 100644 --- a/renumics/spotlight/server.py +++ b/renumics/spotlight/server.py @@ -2,6 +2,7 @@ Local proxy object for the spotlight server process """ +import platform import threading from queue import Queue, Empty import socket @@ -151,7 +152,10 @@ def start(self, config: AppConfig) -> None: # start uvicorn # pylint: disable=consider-using-with self.process = subprocess.Popen( - command, env=env, pass_fds=(self._sock.fileno(),) + command, + env=env, + pass_fds=None if platform.system() == "Windows" else (self._sock.fileno(),), + close_fds=platform.system() != "Windows", ) self._startup_complete_event.wait(timeout=120) From 077776327baa8c302cab607f88e28788f5cc885f Mon Sep 17 00:00:00 2001 From: Alexander Druz Date: Tue, 11 Jul 2023 15:50:57 +0200 Subject: [PATCH 4/7] Close socket after launching child process --- renumics/spotlight/server.py | 74 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/renumics/spotlight/server.py b/renumics/spotlight/server.py index 3329c123..6bbe6fee 100644 --- a/renumics/spotlight/server.py +++ b/renumics/spotlight/server.py @@ -36,7 +36,6 @@ class Server: _host: str _port: int _requested_port: int - _sock: Optional[socket.socket] _vite: Optional[Vite] @@ -65,7 +64,6 @@ def __init__(self, host: str = "127.0.0.1", port: int = 8000) -> None: self._host = host self._requested_port = port self._port = self._requested_port - self._sock = None self.process = None self.connected_frontends = 0 @@ -121,42 +119,42 @@ def start(self, config: AppConfig) -> None: self._vite.start() env["VITE_URL"] = self._vite.url - self._sock = socket.socket() - self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._sock.bind((self._host, self._port)) - self._port = self._sock.getsockname()[1] - - command = [ - sys.executable, - "-m", - "uvicorn", - "renumics.spotlight.app:SpotlightApp", - "--host", - self._host, - "--fd", - str(self._sock.fileno()), - "--log-level", - "critical", - "--http", - "httptools", - "--ws", - "websockets", - "--timeout-graceful-shutdown", - str(2), - "--factory", - ] - - if settings.dev: - command.extend(["--reload"]) - - # start uvicorn - # pylint: disable=consider-using-with - self.process = subprocess.Popen( - command, - env=env, - pass_fds=None if platform.system() == "Windows" else (self._sock.fileno(),), - close_fds=platform.system() != "Windows", - ) + with socket.socket() as sock: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((self._host, self._port)) + self._port = sock.getsockname()[1] + + command = [ + sys.executable, + "-m", + "uvicorn", + "renumics.spotlight.app:SpotlightApp", + "--host", + self._host, + "--fd", + str(sock.fileno()), + "--log-level", + "critical", + "--http", + "httptools", + "--ws", + "websockets", + "--timeout-graceful-shutdown", + str(2), + "--factory", + ] + + if settings.dev: + command.extend(["--reload"]) + + # start uvicorn + # pylint: disable=consider-using-with + self.process = subprocess.Popen( + command, + env=env, + pass_fds=None if platform.system() == "Windows" else (sock.fileno(),), + close_fds=platform.system() != "Windows", + ) self._startup_complete_event.wait(timeout=120) From 860c2bcf633c9a52788710da4fed71c0beca67a3 Mon Sep 17 00:00:00 2001 From: Alexander Druz Date: Tue, 11 Jul 2023 16:06:47 +0200 Subject: [PATCH 5/7] Wait for Uvicorn (child process) to close before server stop --- renumics/spotlight/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/renumics/spotlight/server.py b/renumics/spotlight/server.py index 6bbe6fee..7cb35e75 100644 --- a/renumics/spotlight/server.py +++ b/renumics/spotlight/server.py @@ -173,6 +173,7 @@ def stop(self) -> None: self.process.wait(3) except subprocess.TimeoutExpired: self.process.kill() + self.process.wait(timeout=5) self.process = None self._connection_thread.join(0.1) From abaeeac34bd17e40683fd4907532dc36d7e4f0de Mon Sep 17 00:00:00 2001 From: Alexander Druz Date: Tue, 11 Jul 2023 16:12:22 +0200 Subject: [PATCH 6/7] Pass port instead of socket FD on Windows --- renumics/spotlight/server.py | 75 +++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/renumics/spotlight/server.py b/renumics/spotlight/server.py index 7cb35e75..f07b7354 100644 --- a/renumics/spotlight/server.py +++ b/renumics/spotlight/server.py @@ -119,43 +119,46 @@ def start(self, config: AppConfig) -> None: self._vite.start() env["VITE_URL"] = self._vite.url - with socket.socket() as sock: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind((self._host, self._port)) - self._port = sock.getsockname()[1] - - command = [ - sys.executable, - "-m", - "uvicorn", - "renumics.spotlight.app:SpotlightApp", - "--host", - self._host, - "--fd", - str(sock.fileno()), - "--log-level", - "critical", - "--http", - "httptools", - "--ws", - "websockets", - "--timeout-graceful-shutdown", - str(2), - "--factory", - ] - - if settings.dev: - command.extend(["--reload"]) - - # start uvicorn - # pylint: disable=consider-using-with - self.process = subprocess.Popen( - command, - env=env, - pass_fds=None if platform.system() == "Windows" else (sock.fileno(),), - close_fds=platform.system() != "Windows", - ) + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((self._host, self._port)) + self._port = sock.getsockname()[1] + + command = [ + sys.executable, + "-m", + "uvicorn", + "renumics.spotlight.app:SpotlightApp", + "--host", + self._host, + "--log-level", + "critical", + "--http", + "httptools", + "--ws", + "websockets", + "--timeout-graceful-shutdown", + str(2), + "--factory", + ] + if platform.system() == "Windows": + command += ["--port", str(self._port)] + sock.close() + else: + command += ["--fd", str(sock.fileno())] + if settings.dev: + command.extend(["--reload"]) + + # start uvicorn + # pylint: disable=consider-using-with + self.process = subprocess.Popen( + command, + env=env, + pass_fds=None if platform.system() == "Windows" else (sock.fileno(),), + ) + if platform.system() != "Windows": + sock.close() self._startup_complete_event.wait(timeout=120) def stop(self) -> None: From 8d476d5fa419596eb760e2551e5191e93dcdffb1 Mon Sep 17 00:00:00 2001 From: Alexander Druz Date: Tue, 11 Jul 2023 16:24:41 +0200 Subject: [PATCH 7/7] Rollback Ci workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2789603d..1646201b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,7 +120,7 @@ jobs: uses: druzsan/setup-matrix@v1 with: matrix: | - os: ubuntu-latest windows-latest macos-latest, + os: ubuntu-latest, python-version: 3.8 - name: Print matrix run: echo "$MATRIX" | yq -P '{"matrix":.}'