This commit is contained in:
Masen Furer 2025-02-22 16:38:22 +00:00 committed by GitHub
commit ec28d74027
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 33 additions and 55 deletions

View File

@ -580,6 +580,22 @@ class App(MiddlewareMixin, LifespanMixin):
"""
if not self.api:
raise ValueError("The app has not been initialized.")
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
# before compiling the app in a thread to avoid event loop error (REF-2172).
self._apply_decorated_pages()
compile_future = concurrent.futures.ThreadPoolExecutor(max_workers=1).submit(
self._compile
)
compile_future.add_done_callback(
# Force background compile errors to print eagerly
lambda f: f.result()
)
# Wait for the compile to finish in prod mode to ensure all optional endpoints are mounted.
if is_prod_mode():
compile_future.result()
return self.api
def _add_default_endpoints(self):

View File

@ -1,33 +0,0 @@
"""Shims the real reflex app module for running backend server (uvicorn or gunicorn).
Only the app attribute is explicitly exposed.
"""
from concurrent.futures import ThreadPoolExecutor
from reflex import constants
from reflex.utils.exec import is_prod_mode
from reflex.utils.prerequisites import get_and_validate_app
if constants.CompileVars.APP != "app":
raise AssertionError("unexpected variable name for 'app'")
app, app_module = get_and_validate_app(reload=False)
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
# before compiling the app in a thread to avoid event loop error (REF-2172).
app._apply_decorated_pages()
compile_future = ThreadPoolExecutor(max_workers=1).submit(app._compile)
compile_future.add_done_callback(
# Force background compile errors to print eagerly
lambda f: f.result()
)
# Wait for the compile to finish in prod mode to ensure all optional endpoints are mounted.
if is_prod_mode():
compile_future.result()
# ensure only "app" is exposed.
del app_module
del compile_future
del get_and_validate_app
del is_prod_mode
del constants
del ThreadPoolExecutor

View File

@ -15,6 +15,7 @@ from reflex.config import environment, get_config
from reflex.custom_components.custom_components import custom_components_cli
from reflex.state import reset_disk_state_manager
from reflex.utils import console, telemetry
from reflex.utils.exec import should_use_granian
# Disable typer+rich integration for help panels
typer.core.rich = None # pyright: ignore [reportPrivateImportUsage]
@ -192,7 +193,9 @@ def _run(
if frontend:
# Get the app module.
prerequisites.get_compiled_app()
if not should_use_granian():
# Granian fails if the app is already imported.
prerequisites.get_compiled_app()
# Warn if schema is not up to date.
prerequisites.check_schema_up_to_date()

View File

@ -196,22 +196,9 @@ def get_app_module():
Returns:
The app module for the backend.
"""
return f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
config = get_config()
def get_granian_target():
"""Get the Granian target for the backend.
Returns:
The Granian target for the backend.
"""
import reflex
app_module_path = Path(reflex.__file__).parent / "app_module_for_backend.py"
return (
f"{app_module_path!s}:{constants.CompileVars.APP}.{constants.CompileVars.API}"
)
return f"{config.module}:{constants.CompileVars.APP}"
def run_backend(
@ -229,9 +216,6 @@ def run_backend(
frontend_present: Whether the frontend is present.
"""
web_dir = get_web_dir()
# Create a .nocompile file to skip compile for backend.
if web_dir.exists():
(web_dir / constants.NOCOMPILE_FILE).touch()
if not frontend_present:
notify_backend()
@ -240,6 +224,9 @@ def run_backend(
if should_use_granian():
run_granian_backend(host, port, loglevel)
else:
# Create a .nocompile file to skip compile for backend.
if web_dir.exists():
(web_dir / constants.NOCOMPILE_FILE).touch()
run_uvicorn_backend(host, port, loglevel)
@ -313,7 +300,8 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
import uvicorn
uvicorn.run(
app=f"{get_app_module()}.{constants.CompileVars.API}",
app=f"{get_app_module()}",
factory=True,
host=host,
port=port,
log_level=loglevel.value,
@ -339,7 +327,8 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
from granian.log import LogLevels # pyright: ignore [reportMissingImports]
Granian(
target=get_granian_target(),
target=get_app_module(),
factory=True,
address=host,
port=port,
interface=Interfaces.ASGI,
@ -417,6 +406,7 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
*("--host", host),
*("--port", str(port)),
*("--workers", str(_get_backend_workers())),
"--factory",
app_module,
]
if constants.IS_WINDOWS
@ -482,7 +472,8 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
str(port),
"--interface",
str(Interfaces.ASGI),
get_granian_target(),
"--factory",
get_app_module(),
]
processes.new_process(
command,

View File

@ -7,6 +7,7 @@ def test_page_decorator():
def foo_():
return text("foo")
DECORATED_PAGES.clear()
assert len(DECORATED_PAGES) == 0
decorated_foo_ = page()(foo_)
assert decorated_foo_ == foo_