From 901316cac0c5416d88cf498b229a6c44103fdbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chujun=20Chen=F0=9F=8D=AD?= Date: Mon, 12 Feb 2024 10:54:45 -0800 Subject: [PATCH] fix!: avoid long running process when request timeout Previously function framework use 0 timeout which is actually "no timeout" restrction. This was causing a problem that when user provides a request timeout to Cloud function, process will still continue and consume resources. In this fix, timeout is enabled; default timeout settings is 5 min, same as Cloud run. To make sure timeout settings will be respected, default settings switched from multi-threads to multi-workers. However, user is still allowed to customize workers/threads by assigning env var. But user need to note that timeout won't work when #thread > 1. --- src/functions_framework/__init__.py | 12 ++++++------ src/functions_framework/_http/gunicorn.py | 7 ++++--- src/functions_framework/_typed_event.py | 6 +++--- tests/test_http.py | 12 ++++++------ 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/functions_framework/__init__.py b/src/functions_framework/__init__.py index ece4f446..8c23e5c0 100644 --- a/src/functions_framework/__init__.py +++ b/src/functions_framework/__init__.py @@ -65,9 +65,9 @@ def write(self, out): def cloud_event(func: CloudEventFunction) -> CloudEventFunction: """Decorator that registers cloudevent as user function signature type.""" - _function_registry.REGISTRY_MAP[ - func.__name__ - ] = _function_registry.CLOUDEVENT_SIGNATURE_TYPE + _function_registry.REGISTRY_MAP[func.__name__] = ( + _function_registry.CLOUDEVENT_SIGNATURE_TYPE + ) @functools.wraps(func) def wrapper(*args, **kwargs): @@ -105,9 +105,9 @@ def wrapper(*args, **kwargs): def http(func: HTTPFunction) -> HTTPFunction: """Decorator that registers http as user function signature type.""" - _function_registry.REGISTRY_MAP[ - func.__name__ - ] = _function_registry.HTTP_SIGNATURE_TYPE + _function_registry.REGISTRY_MAP[func.__name__] = ( + _function_registry.HTTP_SIGNATURE_TYPE + ) @functools.wraps(func) def wrapper(*args, **kwargs): diff --git a/src/functions_framework/_http/gunicorn.py b/src/functions_framework/_http/gunicorn.py index 3a9c545b..009a06b7 100644 --- a/src/functions_framework/_http/gunicorn.py +++ b/src/functions_framework/_http/gunicorn.py @@ -21,14 +21,15 @@ class GunicornApplication(gunicorn.app.base.BaseApplication): def __init__(self, app, host, port, debug, **options): self.options = { "bind": "%s:%s" % (host, port), - "workers": 1, - "threads": (os.cpu_count() or 1) * 4, - "timeout": 0, + "workers": os.environ.get("WORKERS", (os.cpu_count() or 1) * 4), + "threads": os.environ.get("THREADS", 1), + "timeout": os.environ.get("CLOUD_RUN_TIMEOUT_SECONDS", 300), "loglevel": "error", "limit_request_line": 0, } self.options.update(options) self.app = app + super().__init__() def load_config(self): diff --git a/src/functions_framework/_typed_event.py b/src/functions_framework/_typed_event.py index 40e715ae..413c8f05 100644 --- a/src/functions_framework/_typed_event.py +++ b/src/functions_framework/_typed_event.py @@ -48,9 +48,9 @@ def register_typed_event(decorator_type, func): ) _function_registry.INPUT_TYPE_MAP[func.__name__] = input_type - _function_registry.REGISTRY_MAP[ - func.__name__ - ] = _function_registry.TYPED_SIGNATURE_TYPE + _function_registry.REGISTRY_MAP[func.__name__] = ( + _function_registry.TYPED_SIGNATURE_TYPE + ) """ Checks whether the response type of the typed function has a to_dict method""" diff --git a/tests/test_http.py b/tests/test_http.py index fbfac9d2..0a46fbea 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -97,17 +97,17 @@ def test_gunicorn_application(debug): assert gunicorn_app.app == app assert gunicorn_app.options == { "bind": "%s:%s" % (host, port), - "workers": 1, - "threads": os.cpu_count() * 4, - "timeout": 0, + "workers": os.cpu_count() * 4, + "threads": 1, + "timeout": 300, "loglevel": "error", "limit_request_line": 0, } assert gunicorn_app.cfg.bind == ["1.2.3.4:1234"] - assert gunicorn_app.cfg.workers == 1 - assert gunicorn_app.cfg.threads == os.cpu_count() * 4 - assert gunicorn_app.cfg.timeout == 0 + assert gunicorn_app.cfg.workers == os.cpu_count() * 4 + assert gunicorn_app.cfg.threads == 1 + assert gunicorn_app.cfg.timeout == 300 assert gunicorn_app.load() == app