[FIX] - formatter/linter, prevent failed backend server start
This commit is contained in:
parent
02858dc425
commit
1ce3569fbf
@ -745,8 +745,11 @@ class Config(Base):
|
|||||||
"gunicorn_max_requests_jitter",
|
"gunicorn_max_requests_jitter",
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
console.warn(
|
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.'
|
'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
|
@property
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""The base for CustomBackendServer."""
|
"""The base for CustomBackendServer."""
|
||||||
|
# ruff: noqa: RUF009
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -297,11 +298,7 @@ class CustomBackendServer:
|
|||||||
else:
|
else:
|
||||||
need_threads = self.get_max_threads(wait_time_ms, service_time_ms)
|
need_threads = self.get_max_threads(wait_time_ms, service_time_ms)
|
||||||
|
|
||||||
return int(
|
return int(min(need_threads, max_available_threads))
|
||||||
max_available_threads
|
|
||||||
if need_threads > max_available_threads
|
|
||||||
else need_threads
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_fields(self) -> dict[str, Field]:
|
def get_fields(self) -> dict[str, Field]:
|
||||||
"""Return all the fields.
|
"""Return all the fields.
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""The GranianBackendServer."""
|
"""The GranianBackendServer."""
|
||||||
|
# ruff: noqa: RUF009
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from dataclasses import field as dc_field
|
from dataclasses import field as dc_field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -200,7 +200,11 @@ class GranianBackendServer(CustomBackendServer):
|
|||||||
return self.address, self.port
|
return self.address, self.port
|
||||||
|
|
||||||
def check_import(self):
|
def check_import(self):
|
||||||
"""Check package importation."""
|
"""Check package importation.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ImportError: raise when some required packaging missing.
|
||||||
|
"""
|
||||||
from importlib.util import find_spec
|
from importlib.util import find_spec
|
||||||
|
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
@ -217,7 +221,7 @@ class GranianBackendServer(CustomBackendServer):
|
|||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
console.error("\n".join(errors))
|
console.error("\n".join(errors))
|
||||||
sys.exit()
|
raise ImportError()
|
||||||
|
|
||||||
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||||
"""Setup.
|
"""Setup.
|
||||||
@ -228,6 +232,7 @@ class GranianBackendServer(CustomBackendServer):
|
|||||||
loglevel (LogLevel): log level
|
loglevel (LogLevel): log level
|
||||||
env (Env): prod/dev environment
|
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._app_uri = self.get_app_module(for_granian_target=True, add_extra_api=True) # type: ignore
|
||||||
self.log_level = loglevel.value # type: ignore
|
self.log_level = loglevel.value # type: ignore
|
||||||
self.address = host
|
self.address = host
|
||||||
@ -235,25 +240,17 @@ class GranianBackendServer(CustomBackendServer):
|
|||||||
self.interface = "asgi" # NOTE: prevent obvious error
|
self.interface = "asgi" # NOTE: prevent obvious error
|
||||||
self._env = env # type: ignore
|
self._env = env # type: ignore
|
||||||
|
|
||||||
if env == Env.PROD:
|
if self.workers == self.get_fields()["workers"].default:
|
||||||
if self.workers == self.get_fields()["workers"].default:
|
self.workers = self.get_recommended_workers()
|
||||||
self.workers = self.get_recommended_workers()
|
else:
|
||||||
else:
|
if self.workers > (max_workers := self.get_max_workers()):
|
||||||
if self.workers > (max_workers := self.get_max_workers()):
|
self.workers = max_workers
|
||||||
self.workers = max_workers
|
|
||||||
|
|
||||||
if self.threads == self.get_fields()["threads"].default:
|
if self.threads == self.get_fields()["threads"].default:
|
||||||
self.threads = self.get_recommended_threads()
|
self.threads = self.get_recommended_threads()
|
||||||
else:
|
else:
|
||||||
if self.threads > (max_threads := self.get_max_threads()):
|
if self.threads > (max_threads := self.get_max_threads()):
|
||||||
self.threads = 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"]
|
|
||||||
|
|
||||||
def run_prod(self):
|
def run_prod(self):
|
||||||
"""Run in production mode.
|
"""Run in production mode.
|
||||||
@ -272,7 +269,7 @@ class GranianBackendServer(CustomBackendServer):
|
|||||||
):
|
):
|
||||||
command += field.metadata["cli"](value).split(" ")
|
command += field.metadata["cli"](value).split(" ")
|
||||||
|
|
||||||
return command + [self._app_uri]
|
return [*command, self._app_uri]
|
||||||
|
|
||||||
def run_dev(self):
|
def run_dev(self):
|
||||||
"""Run in development mode."""
|
"""Run in development mode."""
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""The GunicornBackendServer."""
|
"""The GunicornBackendServer."""
|
||||||
|
# ruff: noqa: RUF009
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Callable, Literal
|
from typing import Any, Callable, Literal
|
||||||
|
|
||||||
@ -288,7 +288,11 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
return host, int(port)
|
return host, int(port)
|
||||||
|
|
||||||
def check_import(self):
|
def check_import(self):
|
||||||
"""Check package importation."""
|
"""Check package importation.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ImportError: raise when some required packaging missing.
|
||||||
|
"""
|
||||||
from importlib.util import find_spec
|
from importlib.util import find_spec
|
||||||
|
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
@ -305,7 +309,7 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
console.error("\n".join(errors))
|
console.error("\n".join(errors))
|
||||||
sys.exit()
|
raise ImportError()
|
||||||
|
|
||||||
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||||
"""Setup.
|
"""Setup.
|
||||||
@ -316,27 +320,23 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
loglevel (LogLevel): log level
|
loglevel (LogLevel): log level
|
||||||
env (Env): prod/dev environment
|
env (Env): prod/dev environment
|
||||||
"""
|
"""
|
||||||
|
self.check_import()
|
||||||
self._app_uri = f"{self.get_app_module()}()" # type: ignore
|
self._app_uri = f"{self.get_app_module()}()" # type: ignore
|
||||||
self.loglevel = loglevel.value # type: ignore
|
self.loglevel = loglevel.value # type: ignore
|
||||||
self.bind = [f"{host}:{port}"]
|
self.bind = [f"{host}:{port}"]
|
||||||
self._env = env # type: ignore
|
self._env = env # type: ignore
|
||||||
|
|
||||||
if env == Env.PROD:
|
if self.workers == self.get_fields()["workers"].default:
|
||||||
if self.workers == self.get_fields()["workers"].default:
|
self.workers = self.get_recommended_workers()
|
||||||
self.workers = self.get_recommended_workers()
|
else:
|
||||||
else:
|
if self.workers > (max_threads := self.get_max_workers()):
|
||||||
if self.workers > (max_threads := self.get_max_workers()):
|
self.workers = max_threads
|
||||||
self.workers = max_threads
|
|
||||||
|
|
||||||
if self.threads == self.get_fields()["threads"].default:
|
if self.threads == self.get_fields()["threads"].default:
|
||||||
self.threads = self.get_recommended_threads()
|
self.threads = self.get_recommended_threads()
|
||||||
else:
|
else:
|
||||||
if self.threads > (max_threads := self.get_max_threads()):
|
if self.threads > (max_threads := self.get_max_threads()):
|
||||||
self.threads = max_threads
|
self.threads = max_threads
|
||||||
self.preload_app = True
|
|
||||||
|
|
||||||
if env == Env.DEV:
|
|
||||||
self.reload = True
|
|
||||||
|
|
||||||
def run_prod(self) -> list[str]:
|
def run_prod(self) -> list[str]:
|
||||||
"""Run in production mode.
|
"""Run in production mode.
|
||||||
@ -355,7 +355,7 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
):
|
):
|
||||||
command += field.metadata["cli"](value).split(" ")
|
command += field.metadata["cli"](value).split(" ")
|
||||||
|
|
||||||
return command + [self._app_uri]
|
return [*command, self._app_uri]
|
||||||
|
|
||||||
def run_dev(self):
|
def run_dev(self):
|
||||||
"""Run in development mode."""
|
"""Run in development mode."""
|
||||||
@ -404,7 +404,3 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
"""Shutdown the backend server."""
|
"""Shutdown the backend server."""
|
||||||
if self._app and self._env == Env.DEV:
|
if self._app and self._env == Env.DEV:
|
||||||
self._app.stop() # type: ignore
|
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
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""The UvicornBackendServer."""
|
"""The UvicornBackendServer."""
|
||||||
|
# ruff: noqa: RUF009
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -6,10 +7,8 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
|
||||||
from configparser import RawConfigParser
|
from configparser import RawConfigParser
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
|
||||||
from typing import IO, Any, Awaitable, Callable
|
from typing import IO, Any, Awaitable, Callable
|
||||||
|
|
||||||
from uvicorn import Config, Server
|
from uvicorn import Config, Server
|
||||||
@ -192,7 +191,11 @@ class UvicornBackendServer(CustomBackendServer):
|
|||||||
return self.host, self.port
|
return self.host, self.port
|
||||||
|
|
||||||
def check_import(self):
|
def check_import(self):
|
||||||
"""Check package importation."""
|
"""Check package importation.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ImportError: raise when some required packaging missing.
|
||||||
|
"""
|
||||||
from importlib.util import find_spec
|
from importlib.util import find_spec
|
||||||
|
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
@ -211,7 +214,7 @@ class UvicornBackendServer(CustomBackendServer):
|
|||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
console.error("\n".join(errors))
|
console.error("\n".join(errors))
|
||||||
sys.exit()
|
raise ImportError()
|
||||||
|
|
||||||
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||||
"""Setup.
|
"""Setup.
|
||||||
@ -222,24 +225,18 @@ class UvicornBackendServer(CustomBackendServer):
|
|||||||
loglevel (LogLevel): log level
|
loglevel (LogLevel): log level
|
||||||
env (Env): prod/dev environment
|
env (Env): prod/dev environment
|
||||||
"""
|
"""
|
||||||
|
self.check_import()
|
||||||
self._app_uri = self.get_app_module(add_extra_api=True) # type: ignore
|
self._app_uri = self.get_app_module(add_extra_api=True) # type: ignore
|
||||||
self.log_level = loglevel.value
|
self.log_level = loglevel.value
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self._env = env # type: ignore
|
self._env = env # type: ignore
|
||||||
|
|
||||||
if env == Env.PROD:
|
if self.workers == self.get_fields()["workers"].default:
|
||||||
if self.workers == self.get_fields()["workers"].default:
|
self.workers = self.get_recommended_workers()
|
||||||
self.workers = self.get_recommended_workers()
|
else:
|
||||||
else:
|
if self.workers > (max_workers := self.get_max_workers()):
|
||||||
if self.workers > (max_workers := self.get_max_workers()):
|
self.workers = 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))]
|
|
||||||
|
|
||||||
def run_prod(self) -> list[str]:
|
def run_prod(self) -> list[str]:
|
||||||
"""Run in production mode.
|
"""Run in production mode.
|
||||||
@ -258,7 +255,7 @@ class UvicornBackendServer(CustomBackendServer):
|
|||||||
):
|
):
|
||||||
command += field.metadata["cli"](value).split(" ")
|
command += field.metadata["cli"](value).split(" ")
|
||||||
|
|
||||||
return command + [self._app_uri]
|
return [*command, self._app_uri]
|
||||||
|
|
||||||
def run_dev(self):
|
def run_dev(self):
|
||||||
"""Run in development mode."""
|
"""Run in development mode."""
|
||||||
@ -279,7 +276,3 @@ class UvicornBackendServer(CustomBackendServer):
|
|||||||
"""Shutdown the backend server."""
|
"""Shutdown the backend server."""
|
||||||
if self._app and self._env == Env.DEV:
|
if self._app and self._env == Env.DEV:
|
||||||
self._app.shutdown() # type: ignore
|
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
|
|
||||||
|
@ -10,6 +10,7 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from threading import Barrier, Event
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
@ -22,6 +23,8 @@ from reflex.utils.prerequisites import get_web_dir
|
|||||||
|
|
||||||
# For uvicorn windows bug fix (#2335)
|
# For uvicorn windows bug fix (#2335)
|
||||||
frontend_process = None
|
frontend_process = None
|
||||||
|
barrier = Barrier(2)
|
||||||
|
failed_start_signal = Event()
|
||||||
|
|
||||||
|
|
||||||
def detect_package_change(json_file_path: Path) -> str:
|
def detect_package_change(json_file_path: Path) -> str:
|
||||||
@ -61,8 +64,14 @@ def kill(proc_pid: int):
|
|||||||
process.kill()
|
process.kill()
|
||||||
|
|
||||||
|
|
||||||
def notify_backend():
|
def notify_backend(only_backend: bool = False):
|
||||||
"""Output a string notifying where the backend is running."""
|
"""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(
|
console.print(
|
||||||
f"Backend running at: [bold green]http://0.0.0.0:{get_config().backend_port}[/bold green]"
|
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(
|
console.print(
|
||||||
f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
|
f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if backend_present:
|
if backend_present:
|
||||||
notify_backend()
|
barrier.wait()
|
||||||
|
if failed_start_signal.is_set():
|
||||||
|
kill(process.pid)
|
||||||
|
process = None
|
||||||
|
break
|
||||||
|
|
||||||
first_run = False
|
first_run = False
|
||||||
else:
|
else:
|
||||||
console.print("New packages detected: Updating app...")
|
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)
|
kill(process.pid)
|
||||||
process = None
|
process = None
|
||||||
break # for line in process.stdout
|
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
|
break # while True
|
||||||
|
|
||||||
|
|
||||||
@ -198,12 +213,17 @@ def run_backend(
|
|||||||
if web_dir.exists():
|
if web_dir.exists():
|
||||||
(web_dir / constants.NOCOMPILE_FILE).touch()
|
(web_dir / constants.NOCOMPILE_FILE).touch()
|
||||||
|
|
||||||
if not frontend_present:
|
|
||||||
notify_backend()
|
|
||||||
|
|
||||||
# Run the backend in development mode.
|
# Run the backend in development mode.
|
||||||
backend_server_dev = config.backend_server_dev
|
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()
|
backend_server_dev.run_dev()
|
||||||
|
|
||||||
|
|
||||||
@ -225,12 +245,17 @@ def run_backend_prod(
|
|||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
|
|
||||||
if not frontend_present:
|
|
||||||
notify_backend()
|
|
||||||
|
|
||||||
# Run the backend in production mode.
|
# Run the backend in production mode.
|
||||||
backend_server_prod = config.backend_server_prod
|
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(
|
processes.new_process(
|
||||||
backend_server_prod.run_prod(),
|
backend_server_prod.run_prod(),
|
||||||
run=True,
|
run=True,
|
||||||
|
Loading…
Reference in New Issue
Block a user