Compare commits

...

3 Commits

Author SHA1 Message Date
Masen Furer
e1f631649f
Shutdown the executor after compilation is done
Avoid keeping the executor threads around while the process is running.
2024-03-01 12:04:39 -08:00
Masen Furer
b6cfa110af
app_module_for_backend: wait for _compile in prod mode
Prod mode uses separate worker processes that fork from the main process.

If the app is not fully compiled when the fork occurs, any further changes to
the app (like mounting the _upload endpoint) will not be reflected in the
workers. This is not a performance hit because compile is skipped anyway
for backend processes and hot reload is not in the picture for prod mode.
2024-03-01 11:08:24 -08:00
Masen Furer
2ffe4e0d78
Expose reflex.utils.exec.is_prod_mode
Formalize runtime checking of the app's `--env` parameter.
2024-03-01 11:06:29 -08:00
6 changed files with 44 additions and 6 deletions

View File

@ -4,6 +4,7 @@ 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_app, get_compiled_app
if "app" != constants.CompileVars.APP:
@ -11,14 +12,33 @@ if "app" != constants.CompileVars.APP:
app_module = get_app(reload=False)
app = getattr(app_module, constants.CompileVars.APP)
# Force background compile errors to print eagerly
ThreadPoolExecutor(max_workers=1).submit(app.compile_).add_done_callback(
lambda f: f.result()
)
_executor = ThreadPoolExecutor(max_workers=1)
def _done(executor: ThreadPoolExecutor):
def _cb(f):
# Do not leak file handles from the executor itself
executor.shutdown(wait=False)
# Force background compile errors to print eagerly
print("compile done", f.result(), executor)
return _cb
compile_future = _executor.submit(app.compile_)
compile_future.add_done_callback(_done(_executor))
# 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 _done
del _executor
del get_app
del get_compiled_app
del is_prod_mode
del constants
del ThreadPoolExecutor

View File

@ -18,6 +18,7 @@ from reflex.components.component import (
from reflex.config import get_config
from reflex.state import BaseState
from reflex.style import LIGHT_COLOR_MODE
from reflex.utils.exec import is_prod_mode
from reflex.utils.imports import ImportVar
@ -66,7 +67,7 @@ def _compile_theme(theme: dict) -> str:
def _is_dev_mode() -> bool:
return os.environ.get("REFLEX_ENV_MODE", "dev") == "dev"
return not is_prod_mode()
def _compile_contexts(state: Optional[Type[BaseState]], theme: Component) -> str:

View File

@ -2,6 +2,7 @@
from .base import (
COOKIES,
ENV_MODE_ENV_VAR,
IS_WINDOWS,
LOCAL_STORAGE,
POLLING_MAX_HTTP_BUFFER_SIZE,

View File

@ -183,6 +183,9 @@ LOCAL_STORAGE = "local_storage"
# If this env var is set to "yes", App.compile will be a no-op
SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"
# This env var stores the execution mode of the app
ENV_MODE_ENV_VAR = "REFLEX_ENV_MODE"
# Testing variables.
# Testing os env set by pytest when running a test case.
PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"

View File

@ -151,7 +151,7 @@ def _run(
console.set_log_level(loglevel)
# Set env mode in the environment
os.environ["REFLEX_ENV_MODE"] = env.value
os.environ[constants.ENV_MODE_ENV_VAR] = env.value
# Show system info
exec.output_system_info()

View File

@ -294,3 +294,16 @@ def is_testing_env() -> bool:
True if the app is running in under pytest.
"""
return constants.PYTEST_CURRENT_TEST in os.environ
def is_prod_mode() -> bool:
"""Check if the app is running in production mode.
Returns:
True if the app is running in production mode or False if running in dev mode.
"""
current_mode = os.environ.get(
constants.ENV_MODE_ENV_VAR,
constants.Env.DEV.value,
)
return current_mode == constants.Env.PROD.value