Skip to content
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
16 changes: 16 additions & 0 deletions docs/source/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,22 @@ For the non sync workers it just means that the worker process is still
communicating and is not tied to the length of time required to handle a
single request.

.. _timeout-delay:

``timeout_delay``
~~~~~~~~~~~~~~~~~

**Command line:** ``--timeout-delay INT``

**Default:** ``0``

Workers are allowed this much time to start up before the timeout period
is enforced. If the worker is active before this delay expires, the
delay period is cancelled and timeout checks begin immediately.

Value is a positive number or 0. Setting it to 0 has the effect of
no delay period before timeout checks are enforced.

.. _graceful-timeout:

``graceful_timeout``
Expand Down
18 changes: 18 additions & 0 deletions gunicorn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,24 @@ class Timeout(Setting):
"""


class TimeoutDelay(Setting):
name = "timeout_delay"
section = "Worker Processes"
cli = ["--timeout-delay"]
meta = "INT"
validator = validate_pos_int
type = int
default = 0
desc = """\
Workers are allowed this much time to start up before the timeout period
is enforced. If the worker is active before this delay expires, the
delay period is cancelled and timeout checks begin immediately.

Value is a positive number or 0. Setting it to 0 has the effect of
no delay period before timeout checks are enforced.
"""


class GracefulTimeout(Setting):
name = "graceful_timeout"
section = "Worker Processes"
Expand Down
10 changes: 8 additions & 2 deletions gunicorn/workers/workertmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,16 @@ def __init__(self, cfg):
os.close(fd)
raise

def notify(self):
new_time = time.monotonic()
# set the file times in the future if a delay is configured
self._set_time(delay=cfg.timeout_delay)

def _set_time(self, delay=0):
new_time = time.monotonic() + delay
os.utime(self._tmp.fileno(), (new_time, new_time))

def notify(self):
self._set_time()

def last_update(self):
return os.fstat(self._tmp.fileno()).st_mtime

Expand Down
75 changes: 75 additions & 0 deletions tests/workers/test_workertmp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.

import io
from unittest import mock

import pytest

from gunicorn import config
from gunicorn.workers.workertmp import WorkerTmp


@pytest.fixture
def cfg(tmp_path):
c = config.Config()
c.set('worker_tmp_dir', str(tmp_path))
return c


@mock.patch('time.monotonic')
def test_creation(mock_monotonic, cfg):
mock_monotonic.side_effect = [100.0]
wt = WorkerTmp(cfg)

mock_monotonic.assert_called_once()
assert isinstance(wt._tmp, io.IOBase)
assert wt.last_update() == 100.0


@mock.patch('time.monotonic')
def test_creation_with_delay(mock_monotonic, cfg):
mock_monotonic.side_effect = [100.0]
cfg.set('timeout_delay', 50)
wt = WorkerTmp(cfg)

mock_monotonic.assert_called_once()
assert isinstance(wt._tmp, io.IOBase)
assert wt.last_update() == 150.0


@mock.patch('time.monotonic')
def test_notify(mock_monotonic, cfg):
mock_monotonic.side_effect = [100.0, 200.0]
wt = WorkerTmp(cfg)
wt.notify()

mock_monotonic.assert_has_calls([(), ()])
assert wt.last_update() == 200.0


@mock.patch('time.monotonic')
def test_notify_before_delay(mock_monotonic, cfg):
mock_monotonic.side_effect = [100.0, 200.0]
cfg.set('timeout_delay', 300)

wt = WorkerTmp(cfg)
assert wt.last_update() == 400.0
wt.notify()

mock_monotonic.assert_has_calls([(), ()])
assert wt.last_update() == 200.0


@mock.patch('time.monotonic')
def test_notify_after_delay(mock_monotonic, cfg):
mock_monotonic.side_effect = [100.0, 500.0]
cfg.set('timeout_delay', 300)

wt = WorkerTmp(cfg)
assert wt.last_update() == 400.0
wt.notify()

mock_monotonic.assert_has_calls([(), ()])
assert wt.last_update() == 500.0