mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-02 10:41:30 +08:00
Add support for SIGTTIN and SIGTTOU signals to the dirty arbiter, allowing dynamic scaling of dirty workers at runtime without restarting gunicorn. Changes: - Add TTIN/TTOU to DirtyArbiter.SIGNALS - Add num_workers instance variable for dynamic count - Add _get_minimum_workers() to enforce app worker constraints - Add signal handlers for TTIN (increase) and TTOU (decrease) - Update manage_workers() to use dynamic count - Add documentation for dynamic scaling - Add unit tests for signal handling - Add Docker integration tests The minimum worker constraint ensures TTOU cannot reduce workers below what apps require (e.g., if an app has workers=3, minimum is 3). Closes #3489
72 lines
2.0 KiB
Python
72 lines
2.0 KiB
Python
#
|
|
# This file is part of gunicorn released under the MIT license.
|
|
# See the NOTICE for more information.
|
|
|
|
"""Test app with multiple dirty tasks for TTIN/TTOU testing."""
|
|
|
|
import json
|
|
import time
|
|
|
|
from gunicorn.dirty import DirtyApp, get_dirty_client
|
|
|
|
|
|
# Unlimited workers - runs on all dirty workers
|
|
class UnlimitedTask(DirtyApp):
|
|
"""Task that runs on all dirty workers."""
|
|
|
|
def setup(self):
|
|
pass
|
|
|
|
def process(self, data):
|
|
return {"task": "unlimited", "data": data}
|
|
|
|
|
|
# Limited to 2 workers
|
|
class LimitedTask(DirtyApp):
|
|
"""Task limited to 2 workers."""
|
|
|
|
workers = 2
|
|
|
|
def setup(self):
|
|
pass
|
|
|
|
def process(self, data):
|
|
delay = data.get("delay", 0)
|
|
if delay:
|
|
time.sleep(delay)
|
|
return {"task": "limited", "data": data}
|
|
|
|
|
|
def app(environ, start_response):
|
|
"""Simple WSGI app for testing."""
|
|
path = environ.get('PATH_INFO', '/')
|
|
|
|
if path == '/health':
|
|
start_response('200 OK', [('Content-Type', 'text/plain')])
|
|
return [b'OK']
|
|
|
|
if path == '/unlimited':
|
|
try:
|
|
client = get_dirty_client()
|
|
result = client.execute('app:UnlimitedTask', {'test': 'data'})
|
|
start_response('200 OK', [('Content-Type', 'application/json')])
|
|
return [json.dumps(result).encode()]
|
|
except Exception as e:
|
|
start_response('500 Internal Server Error',
|
|
[('Content-Type', 'text/plain')])
|
|
return [str(e).encode()]
|
|
|
|
if path == '/limited':
|
|
try:
|
|
client = get_dirty_client()
|
|
result = client.execute('app:LimitedTask', {'test': 'data'})
|
|
start_response('200 OK', [('Content-Type', 'application/json')])
|
|
return [json.dumps(result).encode()]
|
|
except Exception as e:
|
|
start_response('500 Internal Server Error',
|
|
[('Content-Type', 'text/plain')])
|
|
return [str(e).encode()]
|
|
|
|
start_response('404 Not Found', [('Content-Type', 'text/plain')])
|
|
return [b'Not Found']
|