diff --git a/frameworks/Python/granian/app_rsgi_nogil.py b/frameworks/Python/granian/app_rsgi_nogil.py new file mode 100644 index 00000000000..b87931f61b1 --- /dev/null +++ b/frameworks/Python/granian/app_rsgi_nogil.py @@ -0,0 +1,42 @@ +import json + +JSON_HEADERS = [('content-type', 'application/json')] +PLAINTEXT_HEADERS = [('content-type', 'text/plain; charset=utf-8')] + +# json_dumps = orjson.dumps +json_dumps = json.dumps + + +async def route_json(scope, proto): + proto.response_str( + 200, + JSON_HEADERS, + json_dumps({'message': 'Hello, world!'}) + ) + + +async def route_plaintext(scope, proto): + proto.response_bytes( + 200, + PLAINTEXT_HEADERS, + b'Hello, world!' + ) + + +async def handle_404(scope, proto): + proto.response_bytes( + 404, + PLAINTEXT_HEADERS, + b'Not found' + ) + + +routes = { + '/json': route_json, + '/plaintext': route_plaintext +} + + +def main(scope, proto): + handler = routes.get(scope.path, handle_404) + return handler(scope, proto) diff --git a/frameworks/Python/granian/benchmark_config.json b/frameworks/Python/granian/benchmark_config.json index 140075ee0a3..857d81e68ca 100644 --- a/frameworks/Python/granian/benchmark_config.json +++ b/frameworks/Python/granian/benchmark_config.json @@ -63,7 +63,7 @@ "notes": "", "versus": "uvicorn" }, - "wrk": { + "nogil": { "json_url": "/json", "plaintext_url": "/plaintext", "port": 8080, @@ -77,7 +77,7 @@ "webserver": "granian", "os": "Linux", "database_os": "Linux", - "display_name": "granian [rsgi wrk]", + "display_name": "granian [rsgi nogil]", "notes": "", "versus": "uvicorn" } diff --git a/frameworks/Python/granian/config.toml b/frameworks/Python/granian/config.toml index ade7eab9673..2338f0d4a48 100644 --- a/frameworks/Python/granian/config.toml +++ b/frameworks/Python/granian/config.toml @@ -48,7 +48,7 @@ platform = "None" webserver = "granian" versus = "uvicorn" -[wrk] +[nogil] urls.plaintext = "/plaintext" urls.json = "/json" approach = "Realistic" diff --git a/frameworks/Python/granian/granian-nogil.dockerfile b/frameworks/Python/granian/granian-nogil.dockerfile new file mode 100644 index 00000000000..5f0e4045f40 --- /dev/null +++ b/frameworks/Python/granian/granian-nogil.dockerfile @@ -0,0 +1,16 @@ +FROM ghcr.io/astral-sh/uv:debian-slim + +RUN uv python install 3.14t + +ENV UV_PYTHON=3.14t +ENV PYTHON_GIL=0 + +ADD ./ /granian +WORKDIR /granian + +RUN uv venv +RUN uv pip install -r requirements-nogil.txt + +EXPOSE 8080 + +CMD uv run python run_nogil.py rsgi st diff --git a/frameworks/Python/granian/granian-rsgi.dockerfile b/frameworks/Python/granian/granian-rsgi.dockerfile index cba5380d5fd..1eb5066dfeb 100644 --- a/frameworks/Python/granian/granian-rsgi.dockerfile +++ b/frameworks/Python/granian/granian-rsgi.dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-slim +FROM python:3.13-slim ADD ./ /granian @@ -8,4 +8,4 @@ RUN pip install -r /granian/requirements.txt EXPOSE 8080 -CMD python run.py rsgi mt +CMD python run.py rsgi st diff --git a/frameworks/Python/granian/granian-wrk.dockerfile b/frameworks/Python/granian/granian-wrk.dockerfile deleted file mode 100644 index 50a1e008ae8..00000000000 --- a/frameworks/Python/granian/granian-wrk.dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.10-slim - -ADD ./ /granian - -WORKDIR /granian - -RUN pip install -r /granian/requirements.txt - -EXPOSE 8080 - -CMD python run.py rsgi st diff --git a/frameworks/Python/granian/granian-wsgi.dockerfile b/frameworks/Python/granian/granian-wsgi.dockerfile index 1ecac416f49..366f274ff0c 100644 --- a/frameworks/Python/granian/granian-wsgi.dockerfile +++ b/frameworks/Python/granian/granian-wsgi.dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-slim +FROM python:3.13-slim ADD ./ /granian diff --git a/frameworks/Python/granian/granian.dockerfile b/frameworks/Python/granian/granian.dockerfile index 443c501448f..139ba2300d8 100644 --- a/frameworks/Python/granian/granian.dockerfile +++ b/frameworks/Python/granian/granian.dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-slim +FROM python:3.13-slim ADD ./ /granian @@ -8,4 +8,4 @@ RUN pip install -r /granian/requirements.txt EXPOSE 8080 -CMD python run.py asgi mt +CMD python run.py asgi st diff --git a/frameworks/Python/granian/requirements-nogil.txt b/frameworks/Python/granian/requirements-nogil.txt new file mode 100644 index 00000000000..c49d8ced726 --- /dev/null +++ b/frameworks/Python/granian/requirements-nogil.txt @@ -0,0 +1 @@ +granian[rloop]>=2.5.0,<2.6.0 diff --git a/frameworks/Python/granian/requirements.txt b/frameworks/Python/granian/requirements.txt index dd9715142d3..8fc318454b4 100644 --- a/frameworks/Python/granian/requirements.txt +++ b/frameworks/Python/granian/requirements.txt @@ -1,4 +1,4 @@ asyncpg==0.30.0 -granian[uvloop]>=2.4.0,<2.5.0 +granian[uvloop]>=2.5.0,<2.6.0 jinja2==3.1.6 -orjson==3.10.16 +orjson==3.11.3 diff --git a/frameworks/Python/granian/run_nogil.py b/frameworks/Python/granian/run_nogil.py new file mode 100644 index 00000000000..b11779eea63 --- /dev/null +++ b/frameworks/Python/granian/run_nogil.py @@ -0,0 +1,32 @@ +import multiprocessing +import sys + +from granian import Granian + + +if __name__ == '__main__': + interface = sys.argv[1] + runtime_mode = sys.argv[2] + workers = multiprocessing.cpu_count() + + if interface == "rsgi": + #: leave 25% cpu to the Rust runtime + workers = round(workers * 0.75) + + blocking_threads = None + if interface == "wsgi": + #: we don't run any I/O in WSGI benches + blocking_threads = 1 + + Granian( + f"app_{interface}_nogil:main", + address="0.0.0.0", + port=8080, + workers=workers, + runtime_mode=runtime_mode, + blocking_threads=blocking_threads, + backlog=16384, + interface=interface, + http="1", + websockets=False + ).serve()