diff --git a/uvicorn/protocols/http/flow_control.py b/uvicorn/protocols/http/flow_control.py index 2d1b5fa2d..b8399a904 100644 --- a/uvicorn/protocols/http/flow_control.py +++ b/uvicorn/protocols/http/flow_control.py @@ -1,4 +1,5 @@ import asyncio +from dataclasses import dataclass, field from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope @@ -7,12 +8,14 @@ HIGH_WATER_LIMIT = 65536 +@dataclass(slots=True, repr=False, eq=False) class FlowControl: - def __init__(self, transport: asyncio.Transport) -> None: - self._transport = transport - self.read_paused = False - self.write_paused = False - self._is_writable_event = asyncio.Event() + _transport: asyncio.Transport + read_paused: bool = False + write_paused: bool = False + _is_writable_event: asyncio.Event = field(default_factory=asyncio.Event) + + def __post_init__(self) -> None: self._is_writable_event.set() async def drain(self) -> None: diff --git a/uvicorn/protocols/http/h11_impl.py b/uvicorn/protocols/http/h11_impl.py index d7e368888..04720aca1 100644 --- a/uvicorn/protocols/http/h11_impl.py +++ b/uvicorn/protocols/http/h11_impl.py @@ -38,6 +38,33 @@ def _get_status_phrase(status_code: int) -> bytes: class H11Protocol(asyncio.Protocol): + __slots__ = ( + "config", + "server_state", + "app_state", + "loop", + "app", + "logger", + "access_logger", + "access_log", + "conn", + "ws_protocol_class", + "root_path", + "limit_concurrency", + "timeout_keep_alive_task", + "timeout_keep_alive", + "connections", + "tasks", + "transport", + "flow", + "server", + "client", + "scheme", + "scope", + "headers", + "cycle", + ) + def __init__( self, config: Config, diff --git a/uvicorn/protocols/http/httptools_impl.py b/uvicorn/protocols/http/httptools_impl.py index c14513ba9..093ca2eed 100644 --- a/uvicorn/protocols/http/httptools_impl.py +++ b/uvicorn/protocols/http/httptools_impl.py @@ -43,6 +43,36 @@ def _get_status_line(status_code: int) -> bytes: class HttpToolsProtocol(asyncio.Protocol): + __slots__ = ( + "config", + "server_state", + "app_state", + "loop", + "app", + "logger", + "access_logger", + "access_log", + "parser", + "ws_protocol_class", + "root_path", + "limit_concurrency", + "timeout_keep_alive_task", + "timeout_keep_alive", + "connections", + "tasks", + "transport", + "flow", + "server", + "client", + "scheme", + "pipeline", + "scope", + "headers", + "expect_100_continue", + "cycle", + "url", + ) + def __init__( self, config: Config, diff --git a/uvicorn/server.py b/uvicorn/server.py index 938a6aaa9..3ce668bdb 100644 --- a/uvicorn/server.py +++ b/uvicorn/server.py @@ -2,7 +2,6 @@ import asyncio import contextlib -import functools import logging import os import platform @@ -13,6 +12,7 @@ import threading import time from collections.abc import Generator, Sequence +from dataclasses import dataclass, field from email.utils import formatdate from types import FrameType from typing import TYPE_CHECKING, TypeAlias @@ -41,19 +41,32 @@ logger = logging.getLogger("uvicorn.error") +@dataclass(slots=True, repr=False, eq=False) class ServerState: """ Shared servers state that is available between all protocol instances. """ - def __init__(self) -> None: - self.total_requests = 0 - self.connections: set[Protocols] = set() - self.tasks: set[asyncio.Task[None]] = set() - self.default_headers: list[tuple[bytes, bytes]] = [] + total_requests: int = 0 + connections: set[Protocols] = field(default_factory=set) + tasks: set[asyncio.Task[None]] = field(default_factory=set) + default_headers: list[tuple[bytes, bytes]] = field(default_factory=list) class Server: + __slots__ = ( + "config", + "server_state", + "started", + "should_exit", + "force_exit", + "last_notified", + "_captured_signals", + "lifespan", + "servers", + "limit_max_requests", + ) + def __init__(self, config: Config) -> None: self.config = config self.server_state = ServerState() @@ -65,8 +78,9 @@ def __init__(self, config: Config) -> None: self._captured_signals: list[int] = [] - @functools.cached_property - def limit_max_requests(self) -> int | None: + self.limit_max_requests = self._limit_max_requests() + + def _limit_max_requests(self) -> int | None: if self.config.limit_max_requests is None: return None return self.config.limit_max_requests + random.randint(0, self.config.limit_max_requests_jitter)