Skip to content

Commit 1b02275

Browse files
committed
fix: revert to separate GunicornApplication and UvicornApplication classes
Remove the BaseGunicornApplication abstraction as it was causing issues with the timeout mechanism. Each class now independently extends gunicorn.app.base.BaseApplication, which is cleaner and avoids the problems we were seeing with shared state and options handling.
1 parent 11e63db commit 1b02275

File tree

1 file changed

+32
-28
lines changed

1 file changed

+32
-28
lines changed

src/functions_framework/_http/gunicorn.py

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,45 +27,27 @@
2727
TIMEOUT_SECONDS = None
2828

2929

30-
class BaseGunicornApplication(gunicorn.app.base.BaseApplication):
31-
"""Base Gunicorn application with common configuration."""
30+
class GunicornApplication(gunicorn.app.base.BaseApplication):
31+
"""Gunicorn application for WSGI apps with gthread worker support."""
3232

3333
def __init__(self, app, host, port, debug, **options):
3434
global TIMEOUT_SECONDS
3535
TIMEOUT_SECONDS = int(os.environ.get("CLOUD_RUN_TIMEOUT_SECONDS", 0))
36+
threads = int(os.environ.get("THREADS", (os.cpu_count() or 1) * 4))
3637

3738
self.options = {
3839
"bind": "%s:%s" % (host, port),
3940
"workers": int(os.environ.get("WORKERS", 1)),
41+
"threads": threads,
42+
"timeout": TIMEOUT_SECONDS,
4043
"loglevel": os.environ.get("GUNICORN_LOG_LEVEL", "error"),
4144
"limit_request_line": 0,
42-
"timeout": TIMEOUT_SECONDS,
4345
}
44-
4546
self.options.update(options)
4647
self.app = app
4748

4849
super().__init__()
4950

50-
def load_config(self):
51-
for key, value in self.options.items():
52-
self.cfg.set(key, value)
53-
54-
def load(self):
55-
return self.app
56-
57-
58-
class GunicornApplication(BaseGunicornApplication):
59-
"""Gunicorn application for WSGI apps with gthread worker support."""
60-
61-
def __init__(self, app, host, port, debug, **options):
62-
threads = int(os.environ.get("THREADS", (os.cpu_count() or 1) * 4))
63-
# Make a copy to avoid mutating the passed-in options dict
64-
options_copy = dict(options)
65-
options_copy["threads"] = threads
66-
67-
super().__init__(app, host, port, debug, **options_copy)
68-
6951
# Use custom worker with timeout support if conditions are met
7052
if (
7153
TIMEOUT_SECONDS > 0
@@ -78,18 +60,40 @@ def __init__(self, app, host, port, debug, **options):
7860
# Remove timeout from options when using custom worker
7961
del self.options["timeout"]
8062

63+
def load_config(self):
64+
for key, value in self.options.items():
65+
self.cfg.set(key, value)
66+
67+
def load(self):
68+
return self.app
69+
8170

8271
class GThreadWorkerWithTimeoutSupport(ThreadWorker): # pragma: no cover
8372
def handle_request(self, req, conn):
8473
with ThreadingTimeout(TIMEOUT_SECONDS):
8574
super(GThreadWorkerWithTimeoutSupport, self).handle_request(req, conn)
8675

8776

88-
class UvicornApplication(BaseGunicornApplication):
77+
class UvicornApplication(gunicorn.app.base.BaseApplication):
8978
"""Gunicorn application for ASGI apps using Uvicorn workers."""
9079

9180
def __init__(self, app, host, port, debug, **options):
92-
# Make a copy to avoid mutating the passed-in options dict
93-
options_copy = dict(options)
94-
options_copy["worker_class"] = "uvicorn_worker.UvicornWorker"
95-
super().__init__(app, host, port, debug, **options_copy)
81+
self.options = {
82+
"bind": "%s:%s" % (host, port),
83+
"workers": int(os.environ.get("WORKERS", 1)),
84+
"worker_class": "uvicorn_worker.UvicornWorker",
85+
"timeout": int(os.environ.get("CLOUD_RUN_TIMEOUT_SECONDS", 0)),
86+
"loglevel": os.environ.get("GUNICORN_LOG_LEVEL", "error"),
87+
"limit_request_line": 0,
88+
}
89+
self.options.update(options)
90+
self.app = app
91+
92+
super().__init__()
93+
94+
def load_config(self):
95+
for key, value in self.options.items():
96+
self.cfg.set(key, value)
97+
98+
def load(self):
99+
return self.app

0 commit comments

Comments
 (0)