From 89d1c60bed5ef78da0b034dcda63d52881c4496e Mon Sep 17 00:00:00 2001 From: Arek Mateusiak Date: Wed, 15 Apr 2026 17:49:43 +0200 Subject: [PATCH] feat: Enhance config loading by applying GUNICORN_CMD_ARGS before programmatic options (#12313) * feat: Enhance config loading by applying GUNICORN_CMD_ARGS before programmatic options * docs: Add Gunicorn configuration details to environment variables documentation * Update src/backend/base/langflow/server.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/backend/base/langflow/server.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add unit test --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida Co-authored-by: Jordan Frazier <122494242+jordanrfrazier@users.noreply.github.com> Co-authored-by: Jordan Frazier (cherry picked from commit 9d14d3b34ede552168500d168eab25ad5accb0b0) --- docs/docs/Develop/environment-variables.mdx | 14 ++++++ src/backend/base/langflow/server.py | 12 +++++ src/backend/tests/unit/base/test_server.py | 55 +++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/backend/tests/unit/base/test_server.py diff --git a/docs/docs/Develop/environment-variables.mdx b/docs/docs/Develop/environment-variables.mdx index 3acf554748..2e76f494e6 100644 --- a/docs/docs/Develop/environment-variables.mdx +++ b/docs/docs/Develop/environment-variables.mdx @@ -417,6 +417,20 @@ The following environment variables set base Langflow server configuration, such | `LANGFLOW_CELERY_ENABLED` | Boolean | `False` | Enable Celery for distributed task processing. | | `LANGFLOW_ALEMBIC_LOG_TO_STDOUT` | Boolean | `False` | Whether to log Alembic database migration output to stdout instead of a log file. If `true`, Alembic logs to `stdout` and the default log file is ignored. | +#### Gunicorn configuration + +When running Langflow in a production environment using Gunicorn, you can configure Gunicorn workers using the `GUNICORN_CMD_ARGS` environment variable. + +This is particularly useful to manage worker memory and prevent potential memory leaks over time. You can add it to your `.env` file like this: + +```text +GUNICORN_CMD_ARGS="--max-requests 100 --max-requests-jitter 20" +``` + +Here is what these arguments do: +- `--max-requests 100`: Automatically restarts a worker after it has processed 100 requests. This helps prevent memory leaks from accumulating over time. +- `--max-requests-jitter 20`: Adds a random jitter of up to 20 requests to the `--max-requests` value. This ensures that all workers don't restart simultaneously, which could cause a brief downtime or spikes in latency. + For more information about deploying Langflow servers, see [Langflow deployment overview](/deployment-overview). ### Storage diff --git a/src/backend/base/langflow/server.py b/src/backend/base/langflow/server.py index 2a7378de1c..cd2902607a 100644 --- a/src/backend/base/langflow/server.py +++ b/src/backend/base/langflow/server.py @@ -71,6 +71,18 @@ class LangflowApplication(BaseApplication): super().__init__() def load_config(self) -> None: + # Apply options from GUNICORN_CMD_ARGS env var before programmatic options + parser = self.cfg.parser() + cmd_args = self.cfg.get_cmd_args_from_env() + if cmd_args: + env_args = parser.parse_args(cmd_args) + for k, v in vars(env_args).items(): + # Skip unset/positional args and only apply known settings + if v is None or k == "args" or k not in self.cfg.settings: + continue + self.cfg.set(k.lower(), v) + + # Programmatic options override env args config = {key: value for key, value in self.options.items() if key in self.cfg.settings and value is not None} for key, value in config.items(): self.cfg.set(key.lower(), value) diff --git a/src/backend/tests/unit/base/test_server.py b/src/backend/tests/unit/base/test_server.py new file mode 100644 index 0000000000..5c900630ed --- /dev/null +++ b/src/backend/tests/unit/base/test_server.py @@ -0,0 +1,55 @@ +import pytest + +from langflow.server import LangflowApplication + + +def _make_app(options=None, env_args=None, monkeypatch=None): + """Create a LangflowApplication with a dummy WSGI app. + + Args: + options: Programmatic options passed to LangflowApplication. + env_args: If provided, set GUNICORN_CMD_ARGS env var before construction. + monkeypatch: pytest monkeypatch fixture for env manipulation. + """ + if env_args is not None and monkeypatch is not None: + monkeypatch.setenv("GUNICORN_CMD_ARGS", env_args) + + def dummy_app(environ, start_response): + pass + + return LangflowApplication(dummy_app, options=options) + + +class TestGunicornEnvArgs: + def test_env_args_applied(self, monkeypatch): + """GUNICORN_CMD_ARGS values should be reflected in the config.""" + app = _make_app(env_args="--max-requests 100 --max-requests-jitter 20", monkeypatch=monkeypatch) + + assert app.cfg.settings["max_requests"].get() == 100 + assert app.cfg.settings["max_requests_jitter"].get() == 20 + + def test_programmatic_options_override_env(self, monkeypatch): + """Programmatic options must take precedence over GUNICORN_CMD_ARGS.""" + app = _make_app( + options={"workers": 2}, + env_args="--workers 8", + monkeypatch=monkeypatch, + ) + + assert app.cfg.settings["workers"].get() == 2 + + def test_no_env_var_uses_defaults(self): + """Without GUNICORN_CMD_ARGS, Gunicorn defaults should remain intact.""" + app = _make_app() + + # Gunicorn default for max_requests is 0 (disabled) + assert app.cfg.settings["max_requests"].get() == 0 + + def test_env_does_not_override_worker_class(self, monkeypatch): + """worker_class is always set programmatically and must not be overridden by env.""" + app = _make_app( + env_args="--worker-class sync", + monkeypatch=monkeypatch, + ) + + assert app.cfg.settings["worker_class"].get() == "langflow.server.LangflowUvicornWorker"