[FIX] - formatter/linter, prevent failed backend server start

This commit is contained in:
KronosDev-Pro 2025-01-12 05:24:56 +00:00
parent 02858dc425
commit 1ce3569fbf
6 changed files with 96 additions and 85 deletions

View File

@ -745,8 +745,11 @@ class Config(Base):
"gunicorn_max_requests_jitter",
)
):
console.warn(
'The following reflex configuration fields are obsolete: "timeout", "gunicorn_worker_class", "gunicorn_workers", "gunicorn_max_requests", "gunicorn_max_requests_jitter"\nplease update your configuration.'
console.deprecate(
'The following reflex configuration fields are obsolete: "timeout", "gunicorn_worker_class", "gunicorn_workers", "gunicorn_max_requests", "gunicorn_max_requests_jitter"\nplease update your configuration.',
reason="Use `config.backend_server_dev` or `config.backend_server_prod` instead in your `rxconfig.py`.",
deprecation_version="0.7.x",
removal_version="x.x.x",
)
@property

View File

@ -1,4 +1,5 @@
"""The base for CustomBackendServer."""
# ruff: noqa: RUF009
from __future__ import annotations
@ -297,11 +298,7 @@ class CustomBackendServer:
else:
need_threads = self.get_max_threads(wait_time_ms, service_time_ms)
return int(
max_available_threads
if need_threads > max_available_threads
else need_threads
)
return int(min(need_threads, max_available_threads))
def get_fields(self) -> dict[str, Field]:
"""Return all the fields.

View File

@ -1,8 +1,8 @@
"""The GranianBackendServer."""
# ruff: noqa: RUF009
from __future__ import annotations
import sys
from dataclasses import dataclass
from dataclasses import field as dc_field
from pathlib import Path
@ -200,7 +200,11 @@ class GranianBackendServer(CustomBackendServer):
return self.address, self.port
def check_import(self):
"""Check package importation."""
"""Check package importation.
Raises:
ImportError: raise when some required packaging missing.
"""
from importlib.util import find_spec
errors: list[str] = []
@ -217,7 +221,7 @@ class GranianBackendServer(CustomBackendServer):
if errors:
console.error("\n".join(errors))
sys.exit()
raise ImportError()
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
"""Setup.
@ -228,6 +232,7 @@ class GranianBackendServer(CustomBackendServer):
loglevel (LogLevel): log level
env (Env): prod/dev environment
"""
self.check_import()
self._app_uri = self.get_app_module(for_granian_target=True, add_extra_api=True) # type: ignore
self.log_level = loglevel.value # type: ignore
self.address = host
@ -235,25 +240,17 @@ class GranianBackendServer(CustomBackendServer):
self.interface = "asgi" # NOTE: prevent obvious error
self._env = env # type: ignore
if env == Env.PROD:
if self.workers == self.get_fields()["workers"].default:
self.workers = self.get_recommended_workers()
else:
if self.workers > (max_workers := self.get_max_workers()):
self.workers = max_workers
if self.workers == self.get_fields()["workers"].default:
self.workers = self.get_recommended_workers()
else:
if self.workers > (max_workers := self.get_max_workers()):
self.workers = max_workers
if self.threads == self.get_fields()["threads"].default:
self.threads = self.get_recommended_threads()
else:
if self.threads > (max_threads := self.get_max_threads()):
self.threads = max_threads
if env == Env.DEV:
from reflex.config import get_config # prevent circular import
self.reload = True
self.reload_paths = [Path(get_config().app_name)]
self.reload_ignore_dirs = [".web"]
if self.threads == self.get_fields()["threads"].default:
self.threads = self.get_recommended_threads()
else:
if self.threads > (max_threads := self.get_max_threads()):
self.threads = max_threads
def run_prod(self):
"""Run in production mode.
@ -272,7 +269,7 @@ class GranianBackendServer(CustomBackendServer):
):
command += field.metadata["cli"](value).split(" ")
return command + [self._app_uri]
return [*command, self._app_uri]
def run_dev(self):
"""Run in development mode."""

View File

@ -1,8 +1,8 @@
"""The GunicornBackendServer."""
# ruff: noqa: RUF009
from __future__ import annotations
import sys
from dataclasses import dataclass
from typing import Any, Callable, Literal
@ -288,7 +288,11 @@ class GunicornBackendServer(CustomBackendServer):
return host, int(port)
def check_import(self):
"""Check package importation."""
"""Check package importation.
Raises:
ImportError: raise when some required packaging missing.
"""
from importlib.util import find_spec
errors: list[str] = []
@ -305,7 +309,7 @@ class GunicornBackendServer(CustomBackendServer):
if errors:
console.error("\n".join(errors))
sys.exit()
raise ImportError()
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
"""Setup.
@ -316,27 +320,23 @@ class GunicornBackendServer(CustomBackendServer):
loglevel (LogLevel): log level
env (Env): prod/dev environment
"""
self.check_import()
self._app_uri = f"{self.get_app_module()}()" # type: ignore
self.loglevel = loglevel.value # type: ignore
self.bind = [f"{host}:{port}"]
self._env = env # type: ignore
if env == Env.PROD:
if self.workers == self.get_fields()["workers"].default:
self.workers = self.get_recommended_workers()
else:
if self.workers > (max_threads := self.get_max_workers()):
self.workers = max_threads
if self.workers == self.get_fields()["workers"].default:
self.workers = self.get_recommended_workers()
else:
if self.workers > (max_threads := self.get_max_workers()):
self.workers = max_threads
if self.threads == self.get_fields()["threads"].default:
self.threads = self.get_recommended_threads()
else:
if self.threads > (max_threads := self.get_max_threads()):
self.threads = max_threads
self.preload_app = True
if env == Env.DEV:
self.reload = True
if self.threads == self.get_fields()["threads"].default:
self.threads = self.get_recommended_threads()
else:
if self.threads > (max_threads := self.get_max_threads()):
self.threads = max_threads
def run_prod(self) -> list[str]:
"""Run in production mode.
@ -355,7 +355,7 @@ class GunicornBackendServer(CustomBackendServer):
):
command += field.metadata["cli"](value).split(" ")
return command + [self._app_uri]
return [*command, self._app_uri]
def run_dev(self):
"""Run in development mode."""
@ -404,7 +404,3 @@ class GunicornBackendServer(CustomBackendServer):
"""Shutdown the backend server."""
if self._app and self._env == Env.DEV:
self._app.stop() # type: ignore
# TODO: complicated because currently `*BackendServer` don't execute the server command, he just create it
# if self._env == Env.PROD:
# pass

View File

@ -1,4 +1,5 @@
"""The UvicornBackendServer."""
# ruff: noqa: RUF009
from __future__ import annotations
@ -6,10 +7,8 @@ from __future__ import annotations
import asyncio
import os
import ssl
import sys
from configparser import RawConfigParser
from dataclasses import dataclass
from pathlib import Path
from typing import IO, Any, Awaitable, Callable
from uvicorn import Config, Server
@ -192,7 +191,11 @@ class UvicornBackendServer(CustomBackendServer):
return self.host, self.port
def check_import(self):
"""Check package importation."""
"""Check package importation.
Raises:
ImportError: raise when some required packaging missing.
"""
from importlib.util import find_spec
errors: list[str] = []
@ -211,7 +214,7 @@ class UvicornBackendServer(CustomBackendServer):
if errors:
console.error("\n".join(errors))
sys.exit()
raise ImportError()
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
"""Setup.
@ -222,24 +225,18 @@ class UvicornBackendServer(CustomBackendServer):
loglevel (LogLevel): log level
env (Env): prod/dev environment
"""
self.check_import()
self._app_uri = self.get_app_module(add_extra_api=True) # type: ignore
self.log_level = loglevel.value
self.host = host
self.port = port
self._env = env # type: ignore
if env == Env.PROD:
if self.workers == self.get_fields()["workers"].default:
self.workers = self.get_recommended_workers()
else:
if self.workers > (max_workers := self.get_max_workers()):
self.workers = max_workers
if env == Env.DEV:
from reflex.config import get_config # prevent circular import
self.reload = True
self.reload_dirs = [str(Path(get_config().app_name))]
if self.workers == self.get_fields()["workers"].default:
self.workers = self.get_recommended_workers()
else:
if self.workers > (max_workers := self.get_max_workers()):
self.workers = max_workers
def run_prod(self) -> list[str]:
"""Run in production mode.
@ -258,7 +255,7 @@ class UvicornBackendServer(CustomBackendServer):
):
command += field.metadata["cli"](value).split(" ")
return command + [self._app_uri]
return [*command, self._app_uri]
def run_dev(self):
"""Run in development mode."""
@ -279,7 +276,3 @@ class UvicornBackendServer(CustomBackendServer):
"""Shutdown the backend server."""
if self._app and self._env == Env.DEV:
self._app.shutdown() # type: ignore
# TODO: hard because currently `*BackendServer` don't execute the server command, he just create it
# if self._env == Env.PROD:
# pass

View File

@ -10,6 +10,7 @@ import re
import subprocess
import sys
from pathlib import Path
from threading import Barrier, Event
from urllib.parse import urljoin
import psutil
@ -22,6 +23,8 @@ from reflex.utils.prerequisites import get_web_dir
# For uvicorn windows bug fix (#2335)
frontend_process = None
barrier = Barrier(2)
failed_start_signal = Event()
def detect_package_change(json_file_path: Path) -> str:
@ -61,8 +64,14 @@ def kill(proc_pid: int):
process.kill()
def notify_backend():
"""Output a string notifying where the backend is running."""
def notify_backend(only_backend: bool = False):
"""Output a string notifying where the backend is running.
Args:
only_backend: Whether the frontend is present.
"""
if not only_backend:
barrier.wait()
console.print(
f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
)
@ -110,8 +119,14 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True):
console.print(
f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
)
if backend_present:
notify_backend()
barrier.wait()
if failed_start_signal.is_set():
kill(process.pid)
process = None
break
first_run = False
else:
console.print("New packages detected: Updating app...")
@ -130,7 +145,7 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True):
kill(process.pid)
process = None
break # for line in process.stdout
if process is not None:
if (process is not None) or (failed_start_signal.is_set() and process is None):
break # while True
@ -198,12 +213,17 @@ def run_backend(
if web_dir.exists():
(web_dir / constants.NOCOMPILE_FILE).touch()
if not frontend_present:
notify_backend()
# Run the backend in development mode.
backend_server_dev = config.backend_server_dev
backend_server_dev.setup(host, port, loglevel, Env.DEV)
try:
backend_server_dev.setup(host, port, loglevel, Env.DEV)
except ImportError:
if frontend_present:
failed_start_signal.set()
barrier.wait() # for unlock frontend server
return
notify_backend(not frontend_present)
backend_server_dev.run_dev()
@ -225,12 +245,17 @@ def run_backend_prod(
config = get_config()
if not frontend_present:
notify_backend()
# Run the backend in production mode.
backend_server_prod = config.backend_server_prod
backend_server_prod.setup(host, port, loglevel, Env.PROD)
try:
backend_server_prod.setup(host, port, loglevel, Env.PROD)
except ImportError:
if frontend_present:
failed_start_signal.set()
barrier.wait() # for unlock frontend server
return
notify_backend(not frontend_present)
processes.new_process(
backend_server_prod.run_prod(),
run=True,