diff --git a/docs_src/src/pages/documentation/en/api_reference/timeout_configuration.mdx b/docs_src/src/pages/documentation/en/api_reference/timeout_configuration.mdx index 412ef1dda..f2778d6c7 100644 --- a/docs_src/src/pages/documentation/en/api_reference/timeout_configuration.mdx +++ b/docs_src/src/pages/documentation/en/api_reference/timeout_configuration.mdx @@ -24,7 +24,7 @@ async def hello(request): app.start( host="0.0.0.0", port=8080, - client_timeout=30, # Client connection timeout (seconds) + client_timeout=30, # Max seconds to read request headers (seconds) keep_alive_timeout=20 # Keep-alive timeout (seconds) ) ``` @@ -46,8 +46,8 @@ python app.py | Parameter | Default | Description | Environment Variable | |-----------|---------|-------------|---------------------| -| `client_timeout` | 30 | Maximum time (seconds) for client request processing | `ROBYN_CLIENT_TIMEOUT` | -| `keep_alive_timeout` | 20 | Time (seconds) to keep idle connections alive | `ROBYN_KEEP_ALIVE_TIMEOUT` | +| `client_timeout` | 30 | Maximum time (seconds) to wait for the client to transmit the complete request headers. This does **not** limit handler execution time or overall request duration — it only guards against slow or stalled clients during the initial header-reading phase. Maps to actix-web's `client_request_timeout`. | `ROBYN_CLIENT_TIMEOUT` | +| `keep_alive_timeout` | 20 | Time (seconds) to keep idle connections alive before closing them | `ROBYN_KEEP_ALIVE_TIMEOUT` | ## Usage Examples @@ -65,7 +65,7 @@ app.start(client_timeout=30) app.start( host="0.0.0.0", port=8080, - client_timeout=60, # Allow longer processing time + client_timeout=60, # More time for clients on slow networks to send headers keep_alive_timeout=15 # Shorter keep-alive for faster turnover ) ``` @@ -75,7 +75,7 @@ app.start( ```python # Development-friendly settings app.start( - client_timeout=300, # Long timeout for debugging + client_timeout=300, # Generous header-read timeout for debugging keep_alive_timeout=60 # Longer keep-alive for testing ) ``` @@ -85,7 +85,7 @@ app.start( ```python # Optimized for load testing with tools like wrk app.start( - client_timeout=10, # Quick timeouts + client_timeout=10, # Quick header-read timeout keep_alive_timeout=5 # Fast connection turnover ) ``` @@ -130,10 +130,12 @@ If you encounter file descriptor exhaustion: - Lower `keep_alive_timeout` (5-15s) - Moderate `client_timeout` (15-30s) -**For long-running operations:** -- Higher `client_timeout` (60-300s) +**For clients on slow networks:** +- Higher `client_timeout` (60-120s) so slow clients have time to send headers - Standard `keep_alive_timeout` (20-30s) +> **Note:** `client_timeout` only controls how long the server waits for request headers — it does not cap handler execution time. If you need to limit how long a handler can run, implement that logic within the handler itself (e.g. with `asyncio.wait_for`). + ## Best Practices diff --git a/docs_src/src/pages/documentation/zh/api_reference/timeout_configuration.mdx b/docs_src/src/pages/documentation/zh/api_reference/timeout_configuration.mdx index e926e17be..07fcc7fd5 100644 --- a/docs_src/src/pages/documentation/zh/api_reference/timeout_configuration.mdx +++ b/docs_src/src/pages/documentation/zh/api_reference/timeout_configuration.mdx @@ -24,7 +24,7 @@ async def hello(request): app.start( host="0.0.0.0", port=8080, - client_timeout=30, # 客户端连接超时(秒) + client_timeout=30, # 读取请求头的最大等待时间(秒) keep_alive_timeout=20, # 保持连接超时(秒) ) ``` @@ -46,7 +46,7 @@ python app.py | 参数 | 默认值 | 描述 | 环境变量 | |------|-------|------|---------| -| `client_timeout` | 30 | 客户端请求处理的最大时间(秒) | `ROBYN_CLIENT_TIMEOUT` | +| `client_timeout` | 30 | 等待客户端发送完整请求头的最大时间(秒)。此参数**不**限制处理函数执行时间或整个请求的持续时间——它仅防止客户端在发送头信息阶段过慢或停滞。对应 actix-web 的 `client_request_timeout`。 | `ROBYN_CLIENT_TIMEOUT` | | `keep_alive_timeout` | 20 | 保持空闲连接的时间(秒) | `ROBYN_KEEP_ALIVE_TIMEOUT` | ## 使用示例 @@ -65,7 +65,7 @@ app.start(client_timeout=30) app.start( host="0.0.0.0", port=8080, - client_timeout=60, # 允许更长的处理时间 + client_timeout=60, # 为慢速网络客户端留更多时间发送请求头 keep_alive_timeout=15, # 更短的保持连接以加快周转 ) ``` @@ -75,7 +75,7 @@ app.start( ```python # 开发友好设置 app.start( - client_timeout=300, # 调试的长超时 + client_timeout=300, # 调试时宽裕的请求头读取超时 keep_alive_timeout=60, # 测试的长保持连接 ) ``` @@ -85,7 +85,7 @@ app.start( ```python # 针对 wrk 等工具的负载测试优化 app.start( - client_timeout=10, # 快速超时 + client_timeout=10, # 快速请求头读取超时 keep_alive_timeout=5, # 快速连接周转 ) ``` @@ -130,10 +130,12 @@ app.start(client_timeout=30) - 较低的 `keep_alive_timeout` (5-15秒) - 中等的 `client_timeout` (15-30秒) -**对于长时间运行的操作:** -- 较高的 `client_timeout` (60-300秒) +**对于慢速网络上的客户端:** +- 较高的 `client_timeout` (60-120秒),为慢速客户端留出发送请求头的时间 - 标准的 `keep_alive_timeout` (20-30秒) +> **注意:** `client_timeout` 仅控制服务器等待请求头的时间——它不会限制处理函数的执行时间。如需限制处理函数运行时长,请在处理函数内部实现该逻辑(例如使用 `asyncio.wait_for`)。 + ## 与其他框架的比较 | 框架 | 默认客户端超时 | 默认保持连接 | 配置方式 | diff --git a/robyn/__init__.py b/robyn/__init__.py index 08dd0631d..ebcae971d 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -633,8 +633,15 @@ def start(self, host: str = "127.0.0.1", port: int = 8080, _check_port: bool = T :param host str: represents the host at which the server is listening :param port int: represents the port number at which the server is listening :param _check_port bool: represents if the port should be checked if it is already in use - :param client_timeout int: timeout for client connections in seconds (default: 30) - :param keep_alive_timeout int: timeout for keep-alive connections in seconds (default: 20) + :param client_timeout int: maximum time in seconds to wait for the client to send + the complete request headers (default: 30). This does **not** limit handler + execution time or the overall request duration — it only guards against slow + or stalled clients during the initial header-reading phase. Under the hood + this maps to actix-web's ``client_request_timeout``. + Can be overridden with the ``ROBYN_CLIENT_TIMEOUT`` environment variable. + :param keep_alive_timeout int: time in seconds to keep an idle connection open + before closing it (default: 20). Can be overridden with the + ``ROBYN_KEEP_ALIVE_TIMEOUT`` environment variable. """ host = os.getenv("ROBYN_HOST", host) diff --git a/robyn/processpool.py b/robyn/processpool.py index e1ca456f3..fb368e48b 100644 --- a/robyn/processpool.py +++ b/robyn/processpool.py @@ -31,6 +31,15 @@ def run_processes( client_timeout: int = 30, keep_alive_timeout: int = 20, ) -> List[Process]: + """ + Spawn server processes. + + :param client_timeout: max seconds to wait for the client to send the + complete request headers (maps to actix-web's + ``client_request_timeout``). This does **not** limit handler execution + time or overall request duration. + :param keep_alive_timeout: seconds to keep idle connections open. + """ socket = SocketHeld(url, port) process_pool = init_processpool( @@ -175,6 +184,11 @@ def spawn_process( :param socket SocketHeld: This is the main tcp socket, which is being shared across multiple processes. :param process_name string: This is the name given to the process to identify the process :param workers int: This is the name given to the process to identify the process + :param client_timeout int: max seconds to wait for the client to send the + complete request headers (maps to actix-web's + ``client_request_timeout``). This does **not** limit handler execution + time or overall request duration. + :param keep_alive_timeout int: seconds to keep idle connections open. """ loop = initialize_event_loop() @@ -218,7 +232,7 @@ def spawn_process( ) try: - server.start(socket, workers) + server.start(socket, workers, client_timeout, keep_alive_timeout) loop = asyncio.get_event_loop() loop.run_forever() except KeyboardInterrupt: diff --git a/robyn/robyn.pyi b/robyn/robyn.pyi index 554a85aa5..92cd5f108 100644 --- a/robyn/robyn.pyi +++ b/robyn/robyn.pyi @@ -515,6 +515,19 @@ class Server: ) -> None: pass def start(self, socket: SocketHeld, workers: int, client_timeout: int, keep_alive_timeout: int) -> None: + """ + Start the Robyn server. + + :param socket: The shared socket to listen on. + :param workers: Number of worker threads. + :param client_timeout: Maximum time in seconds to wait for the client + to transmit the complete request headers. This does **not** limit + handler execution time or overall request duration — it only guards + against slow or stalled clients during the initial header-reading + phase (maps to actix-web's ``client_request_timeout``). + :param keep_alive_timeout: Time in seconds to keep an idle connection + open before closing it. + """ pass class WebSocketConnector: diff --git a/src/server.rs b/src/server.rs index a82b931b8..f0db1b2ff 100644 --- a/src/server.rs +++ b/src/server.rs @@ -81,11 +81,22 @@ impl Server { } } + /// Start the Robyn server. + /// + /// * `client_timeout` – maximum seconds to wait for the client to transmit + /// the complete request headers. This does **not** limit handler execution + /// time or overall request duration; it only guards against slow or + /// stalled clients during the initial header-reading phase. Mapped to + /// actix-web's `client_request_timeout`. + /// * `keep_alive_timeout` – seconds to keep an idle connection open before + /// closing it. Mapped to actix-web's `KeepAlive::Timeout`. pub fn start( &mut self, _py: Python, socket: PyRef, workers: usize, + client_timeout: u64, + keep_alive_timeout: u64, ) -> PyResult<()> { pyo3_log::init(); @@ -275,9 +286,9 @@ impl Server { }, )) }) - .keep_alive(KeepAlive::Os) + .keep_alive(KeepAlive::Timeout(std::time::Duration::from_secs(keep_alive_timeout))) .workers(workers) - .client_request_timeout(std::time::Duration::from_secs(0)) + .client_request_timeout(std::time::Duration::from_secs(client_timeout)) .listen(raw_socket.into()) .unwrap() .run()