You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/05/04 14:53:56 UTC

(tooling-trusted-releases) branch sbp updated: Wait longer for uploads to complete

This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new d81b2def Wait longer for uploads to complete
d81b2def is described below

commit d81b2def27cb3471efb03bdeb493e24d54f54ebe
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Mon May 4 15:53:33 2026 +0100

    Wait longer for uploads to complete
---
 atr/config.py |  2 ++
 atr/server.py | 24 ++++++++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/atr/config.py b/atr/config.py
index 2bf8f47d..a7043cda 100644
--- a/atr/config.py
+++ b/atr/config.py
@@ -117,6 +117,8 @@ class AppConfig:
     APACHE_RAT_JAR_PATH = decouple.config("APACHE_RAT_JAR_PATH", default=f"/opt/tools/apache-rat-{_RAT_VERSION}.jar")
     # Maximum content length for requests
     MAX_CONTENT_LENGTH: int = decouple.config("MAX_CONTENT_LENGTH", default=512 * _MB, cast=int)
+    # Maximum duration to receive upload request bodies
+    UPLOAD_BODY_TIMEOUT: int = decouple.config("UPLOAD_BODY_TIMEOUT", default=3600, cast=int)
     # Maximum size limit for archive extraction
     MAX_EXTRACT_SIZE: int = decouple.config("MAX_EXTRACT_SIZE", default=2 * _GB, cast=int)
     # Chunk size for reading files during extraction
diff --git a/atr/server.py b/atr/server.py
index 7de0be3b..1640876d 100644
--- a/atr/server.py
+++ b/atr/server.py
@@ -483,6 +483,8 @@ def _app_setup_request_lifecycle(app: base.QuartApp) -> None:
 
     logger = structlog.get_logger("atr.request")
 
+    app.before_request(_apply_upload_body_timeout)
+
     @app.before_request
     async def bind_request_context_vars() -> None:
         await _reset_request_log_context()
@@ -633,6 +635,12 @@ async def _app_shutdown_log_listeners(app):
         listener.stop()
 
 
+async def _apply_upload_body_timeout() -> None:
+    if not _uses_upload_body_timeout(quart.request.method, quart.request.path):
+        return
+    quart.request.body_timeout = config.get().UPLOAD_BODY_TIMEOUT
+
+
 async def _backfill_archive_cache() -> None:
     backfill_results = await asyncio.to_thread(quarantine.backfill_archive_cache)
     if backfill_results:
@@ -1021,6 +1029,16 @@ def _register_routes(app: base.QuartApp) -> None:  # noqa: C901
             return quart.jsonify({"error": "413 Payload Too Large"}), 413
         return await template.render("error.html", error="413 Payload Too Large", traceback="", status_code=413), 413
 
+    @app.errorhandler(408)
+    async def handle_request_timeout(error: Exception) -> Any:
+        timeout = quart.request.body_timeout
+        log.warning(f"Request timed out while reading request body after {timeout} seconds")
+        message = f"408 Request Timeout: request body did not finish before the server timeout of {timeout} seconds."
+        wants_json = quart.request.accept_mimetypes.best_match(["application/json", "text/html"]) == "application/json"
+        if quart.request.path.startswith("/api") or wants_json:
+            return quart.jsonify({"error": message}), 408
+        return await template.render("error.html", error=message, traceback="", status_code=408), 408
+
     # Add a global error handler in case a page does not exist.
     @app.errorhandler(404)
     async def handle_not_found(error: Exception) -> Any:
@@ -1078,6 +1096,12 @@ def _set_file_permissions_to_read_only() -> None:
         log.info(f"Set permissions of {fixed_count} files to read only (0o444)")
 
 
+def _uses_upload_body_timeout(method: str, path: str) -> bool:
+    if method != "POST":
+        return False
+    return path.startswith("/upload/") or (path == "/api/release/upload")
+
+
 def _validate_config(app_config: type[config.AppConfig], hot_reload: bool) -> None:
     config.validate()
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org