From e45763193fe496cefc6749c45e6a31e62f987d63 Mon Sep 17 00:00:00 2001 From: Jacob Green Date: Tue, 23 Apr 2019 09:08:05 -0700 Subject: [PATCH] [2.2.x] Fixed #30361 -- Increased the default timeout of watchman client to 5 seconds and made it customizable. Made the default timeout of watchman client customizable via DJANGO_WATCHMAN_TIMEOUT environment variable. Backport of ed3c59097a01ed3f32f8a8bed95307fb5c181251 from master --- AUTHORS | 1 + django/utils/autoreload.py | 5 +++-- docs/ref/django-admin.txt | 5 +++++ docs/releases/2.2.1.txt | 4 ++++ tests/utils_tests/test_autoreload.py | 9 +++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index bdeb2f5925..992e1d9f99 100644 --- a/AUTHORS +++ b/AUTHORS @@ -359,6 +359,7 @@ answer newbie questions, and generally made Django that much better: Jaap Roes Jack Moffitt Jacob Burch + Jacob Green Jacob Kaplan-Moss Jakub Paczkowski Jakub Wilk diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index 33f2b2be96..d43342c2d7 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -366,11 +366,12 @@ class WatchmanReloader(BaseReloader): def __init__(self): self.roots = defaultdict(set) self.processed_request = threading.Event() + self.client_timeout = int(os.environ.get('DJANGO_WATCHMAN_TIMEOUT', 5)) super().__init__() @cached_property def client(self): - return pywatchman.client() + return pywatchman.client(timeout=self.client_timeout) def _watch_root(self, root): # In practice this shouldn't occur, however, it's possible that a @@ -528,7 +529,7 @@ class WatchmanReloader(BaseReloader): def check_availability(cls): if not pywatchman: raise WatchmanUnavailable('pywatchman not installed.') - client = pywatchman.client(timeout=0.01) + client = pywatchman.client(timeout=0.1) try: result = client.capabilityCheck() except Exception: diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 582eabdd3b..80a4303509 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -888,6 +888,11 @@ more robust change detection, and a reduction in power usage. for optimal performance. See the `watchman documentation`_ for information on how to do this. +.. admonition:: Watchman timeout + + The default timeout of ``Watchman`` client is 5 seconds. You can change it + by setting the ``DJANGO_WATCHMAN_TIMEOUT`` environment variable. + .. _Watchman: https://facebook.github.io/watchman/ .. _pywatchman: https://pypi.org/project/pywatchman/ .. _watchman documentation: https://facebook.github.io/watchman/docs/config.html#ignore_dirs diff --git a/docs/releases/2.2.1.txt b/docs/releases/2.2.1.txt index b7b1f6112d..98f800d455 100644 --- a/docs/releases/2.2.1.txt +++ b/docs/releases/2.2.1.txt @@ -55,3 +55,7 @@ Bugfixes :class:`~django.contrib.sessions.middleware.SessionMiddleware` subclasses, rather than requiring :mod:`django.contrib.sessions` to be in :setting:`INSTALLED_APPS` (:ticket:`30312`). + +* Increased the default timeout when using ``Watchman`` to 5 seconds to prevent + falling back to ``StatReloader`` on larger projects and made it customizable + via the ``DJANGO_WATCHMAN_TIMEOUT`` environment variable (:ticket:`30361`). diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 1f3bc0c95b..f47ad46b8b 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -556,6 +556,11 @@ def skip_unless_watchman_available(): class WatchmanReloaderTests(ReloaderTests, IntegrationTests): RELOADER_CLS = autoreload.WatchmanReloader + def setUp(self): + super().setUp() + # Shorten the timeout to speed up tests. + self.reloader.client_timeout = 0.1 + def test_watch_glob_ignores_non_existing_directories_two_levels(self): with mock.patch.object(self.reloader, '_subscribe') as mocked_subscribe: self.reloader._watch_glob(self.tempdir / 'does_not_exist' / 'more', ['*']) @@ -636,6 +641,10 @@ class WatchmanReloaderTests(ReloaderTests, IntegrationTests): self.reloader.update_watches() self.assertIsInstance(mocked_server_status.call_args[0][0], TestException) + @mock.patch.dict(os.environ, {'DJANGO_WATCHMAN_TIMEOUT': '10'}) + def test_setting_timeout_from_environment_variable(self): + self.assertEqual(self.RELOADER_CLS.client_timeout, 10) + class StatReloaderTests(ReloaderTests, IntegrationTests): RELOADER_CLS = autoreload.StatReloader