Skip to content

More predictable timeout-notify with slow sync calls #2625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import asyncio
import contextlib
import importlib
import logging
import signal
import sys
from collections.abc import Generator
from contextlib import AbstractContextManager
from typing import Callable
from unittest import mock

import httpx
import pytest
Expand Down Expand Up @@ -95,3 +97,28 @@ async def test_request_than_limit_max_requests_warn_log(
responses = await asyncio.gather(*tasks)
assert len(responses) == 2
assert "Maximum request limit of 1 exceeded. Terminating process." in caplog.text


async def test_notify_is_triggered_on_every_tick(
unused_tcp_port: int, http_protocol_cls: type[H11Protocol | HttpToolsProtocol], caplog: pytest.LogCaptureFixture
):
server_module = importlib.import_module("uvicorn.server")
notify = mock.AsyncMock()

config = Config(
app=app,
limit_max_requests=1,
port=unused_tcp_port,
http=http_protocol_cls,
callback_notify=notify,
timeout_notify=10,
)

# mocking time() to simulate a slow sync call
with mock.patch.object(server_module.time, "time") as mock_time:
mock_time.return_value = 1e9
async with run_server(config):
assert notify.call_count == 1
mock_time.return_value = 2e9
await asyncio.sleep(0.1) # step forward just one tick
assert notify.call_count == 2
13 changes: 7 additions & 6 deletions uvicorn/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,10 @@ async def main_loop(self) -> None:
should_exit = await self.on_tick(counter)

async def on_tick(self, counter: int) -> bool:
current_time = time.time()

# Update the default headers, once per second.
if counter % 10 == 0:
current_time = time.time()
current_date = formatdate(current_time, usegmt=True).encode()

if self.config.date_header:
Expand All @@ -242,11 +243,11 @@ async def on_tick(self, counter: int) -> bool:

self.server_state.default_headers = date_header + self.config.encoded_headers

# Callback to `callback_notify` once every `timeout_notify` seconds.
if self.config.callback_notify is not None:
if current_time - self.last_notified > self.config.timeout_notify: # pragma: full coverage
self.last_notified = current_time
await self.config.callback_notify()
# Callback to `callback_notify` once every `timeout_notify` seconds.
if self.config.callback_notify is not None:
if current_time - self.last_notified > self.config.timeout_notify: # pragma: full coverage
self.last_notified = current_time
await self.config.callback_notify()

# Determine if we should exit.
if self.should_exit:
Expand Down
Loading