Skip to content

Commit d3b0a0b

Browse files
committed
Add timeout_delay configuration
1 parent 56b5ad8 commit d3b0a0b

File tree

4 files changed

+117
-2
lines changed

4 files changed

+117
-2
lines changed

docs/source/settings.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,6 +1758,22 @@ For the non sync workers it just means that the worker process is still
17581758
communicating and is not tied to the length of time required to handle a
17591759
single request.
17601760

1761+
.. _timeout-delay:
1762+
1763+
``timeout_delay``
1764+
~~~~~~~~~~~~~~~~~
1765+
1766+
**Command line:** ``--timeout-delay INT``
1767+
1768+
**Default:** ``0``
1769+
1770+
Workers are allowed this much time to start up before the timeout period
1771+
is enforced. If the worker is active before this delay expires, the
1772+
delay period is cancelled and timeout checks begin immediately.
1773+
1774+
Value is a positive number or 0. Setting it to 0 has the effect of
1775+
no delay period before timeout checks are enforced.
1776+
17611777
.. _graceful-timeout:
17621778

17631779
``graceful_timeout``

gunicorn/config.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,24 @@ class Timeout(Setting):
798798
"""
799799

800800

801+
class TimeoutDelay(Setting):
802+
name = "timeout_delay"
803+
section = "Worker Processes"
804+
cli = ["--timeout-delay"]
805+
meta = "INT"
806+
validator = validate_pos_int
807+
type = int
808+
default = 0
809+
desc = """\
810+
Workers are allowed this much time to start up before the timeout period
811+
is enforced. If the worker is active before this delay expires, the
812+
delay period is cancelled and timeout checks begin immediately.
813+
814+
Value is a positive number or 0. Setting it to 0 has the effect of
815+
no delay period before timeout checks are enforced.
816+
"""
817+
818+
801819
class GracefulTimeout(Setting):
802820
name = "graceful_timeout"
803821
section = "Worker Processes"

gunicorn/workers/workertmp.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,16 @@ def __init__(self, cfg):
3939
os.close(fd)
4040
raise
4141

42-
def notify(self):
43-
new_time = time.monotonic()
42+
# set the file times in the future if a delay is configured
43+
self._set_time(delay=cfg.timeout_delay)
44+
45+
def _set_time(self, delay=0):
46+
new_time = time.monotonic() + delay
4447
os.utime(self._tmp.fileno(), (new_time, new_time))
4548

49+
def notify(self):
50+
self._set_time()
51+
4652
def last_update(self):
4753
return os.fstat(self._tmp.fileno()).st_mtime
4854

tests/workers/test_workertmp.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#
2+
# This file is part of gunicorn released under the MIT license.
3+
# See the NOTICE for more information.
4+
5+
import io
6+
from unittest import mock
7+
8+
import pytest
9+
10+
from gunicorn import config
11+
from gunicorn.workers.workertmp import WorkerTmp
12+
13+
14+
@pytest.fixture
15+
def cfg(tmp_path):
16+
c = config.Config()
17+
c.set('worker_tmp_dir', str(tmp_path))
18+
return c
19+
20+
21+
@mock.patch('time.monotonic')
22+
def test_creation(mock_monotonic, cfg):
23+
mock_monotonic.side_effect = [100.0]
24+
wt = WorkerTmp(cfg)
25+
26+
mock_monotonic.assert_called_once()
27+
assert isinstance(wt._tmp, io.IOBase)
28+
assert wt.last_update() == 100.0
29+
30+
31+
@mock.patch('time.monotonic')
32+
def test_creation_with_delay(mock_monotonic, cfg):
33+
mock_monotonic.side_effect = [100.0]
34+
cfg.set('timeout_delay', 50)
35+
wt = WorkerTmp(cfg)
36+
37+
mock_monotonic.assert_called_once()
38+
assert isinstance(wt._tmp, io.IOBase)
39+
assert wt.last_update() == 150.0
40+
41+
42+
@mock.patch('time.monotonic')
43+
def test_notify(mock_monotonic, cfg):
44+
mock_monotonic.side_effect = [100.0, 200.0]
45+
wt = WorkerTmp(cfg)
46+
wt.notify()
47+
48+
mock_monotonic.assert_has_calls([(), ()])
49+
assert wt.last_update() == 200.0
50+
51+
52+
@mock.patch('time.monotonic')
53+
def test_notify_before_delay(mock_monotonic, cfg):
54+
mock_monotonic.side_effect = [100.0, 200.0]
55+
cfg.set('timeout_delay', 300)
56+
57+
wt = WorkerTmp(cfg)
58+
assert wt.last_update() == 400.0
59+
wt.notify()
60+
61+
mock_monotonic.assert_has_calls([(), ()])
62+
assert wt.last_update() == 200.0
63+
64+
65+
@mock.patch('time.monotonic')
66+
def test_notify_after_delay(mock_monotonic, cfg):
67+
mock_monotonic.side_effect = [100.0, 500.0]
68+
cfg.set('timeout_delay', 300)
69+
70+
wt = WorkerTmp(cfg)
71+
assert wt.last_update() == 400.0
72+
wt.notify()
73+
74+
mock_monotonic.assert_has_calls([(), ()])
75+
assert wt.last_update() == 500.0

0 commit comments

Comments
 (0)