diff --git a/pynecone/app.py b/pynecone/app.py index 694b12223..05928b552 100644 --- a/pynecone/app.py +++ b/pynecone/app.py @@ -74,6 +74,14 @@ class App(Base): """ return f"" + def __call__(self) -> fastapi.FastAPI: + """Run the backend api instance. + + Returns: + The backend api. + """ + return self.api + def add_default_endpoints(self): """Add the default endpoints.""" # To test the server. diff --git a/pynecone/constants.py b/pynecone/constants.py index 60d9c3bed..e7f700568 100644 --- a/pynecone/constants.py +++ b/pynecone/constants.py @@ -56,12 +56,10 @@ BUN_PATH = "$HOME/.bun/bin/bun" INSTALL_BUN = "curl https://bun.sh/install | bash" # Command to run the backend in dev mode. RUN_BACKEND = "uvicorn --log-level critical --reload --host 0.0.0.0".split() -# The number of workers to run in production mode by default. -NUM_WORKERS = (os.cpu_count() or 1) * 2 + 1 # The default timeout when launching the gunicorn server. TIMEOUT = 120 # The command to run the backend in production mode. -RUN_BACKEND_PROD = f"gunicorn --worker-class uvicorn.workers.UvicornH11Worker --bind 0.0.0.0:8000 --workers {NUM_WORKERS} --threads {NUM_WORKERS} --preload --timeout {TIMEOUT} --log-level debug".split() +RUN_BACKEND_PROD = f"gunicorn --worker-class uvicorn.workers.UvicornH11Worker --bind 0.0.0.0:8000 --preload --timeout {TIMEOUT} --log-level debug".split() # Compiler variables. # The extension for compiled Javascript files. diff --git a/pynecone/state.py b/pynecone/state.py index 4f9362fea..f89f0201f 100644 --- a/pynecone/state.py +++ b/pynecone/state.py @@ -8,6 +8,8 @@ import traceback from abc import ABC from typing import Any, Callable, ClassVar, Dict, List, Optional, Sequence, Set, Type +from redis import Redis + from pynecone import constants, utils from pynecone.base import Base from pynecone.event import Event, EventHandler, window_alert @@ -476,7 +478,7 @@ class StateManager(Base): token_expiration: int = constants.TOKEN_EXPIRATION # The redis client to use. - redis: Any = None + redis: Optional[Redis] = None def setup(self, state: Type[State]): """Set up the state manager. diff --git a/pynecone/utils.py b/pynecone/utils.py index aa23726a3..1d818839a 100644 --- a/pynecone/utils.py +++ b/pynecone/utils.py @@ -29,6 +29,7 @@ from typing import ( import plotly.graph_objects as go from plotly.io import to_json +from redis import Redis from rich.console import Console from pynecone import constants @@ -385,6 +386,20 @@ def run_frontend_prod(app) -> subprocess.Popen: return subprocess.Popen(command, cwd=constants.WEB_DIR) +def get_num_workers() -> int: + """Get the number of backend worker processes. + + Returns: + The number of backend worker processes. + """ + if get_redis() is None: + # If there is no redis, then just use 1 worker. + return 1 + + # Use the number of cores * 2 + 1. + return (os.cpu_count() or 1) * 2 + 1 + + def run_backend(app): """Run the backend. @@ -403,7 +418,14 @@ def run_backend_prod(app) -> None: Args: app: The app. """ - command = constants.RUN_BACKEND_PROD + [f"{app.__name__}:{constants.API_VAR}"] + num_workers = get_num_workers() + command = constants.RUN_BACKEND_PROD + [ + "--workers", + str(num_workers), + "--threads", + str(num_workers), + f"{app.__name__}:{constants.APP_VAR}()", + ] subprocess.call(command) @@ -918,20 +940,15 @@ def get_hydrate_event(state) -> str: return get_event(state, constants.HYDRATE) -def get_redis(): +def get_redis() -> Optional[Redis]: """Get the redis client. Returns: The redis client. """ - try: - import redis # type: ignore - except: - return None - config = get_config() if config.redis_url is None: return None redis_url, redis_port = config.redis_url.split(":") print("Using redis at", config.redis_url) - return redis.Redis(host=redis_url, port=int(redis_port), db=0) + return Redis(host=redis_url, port=int(redis_port), db=0)