From ed3c59097a01ed3f32f8a8bed95307fb5c181251 Mon Sep 17 00:00:00 2001 From: Jacob Green Date: Tue, 23 Apr 2019 09:08:05 -0700 Subject: [PATCH] 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. --- 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 62268ff103..a27fb7b6d2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -364,6 +364,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 427d979a7f..4a68fb05d0 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 0df57475ab..b531978dd6 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -897,6 +897,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 fdfb47797e..c59cb23cc3 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -558,6 +558,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', ['*']) @@ -638,6 +643,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) + @skipIf(on_macos_with_hfs(), "These tests do not work with HFS+ as a filesystem") class StatReloaderTests(ReloaderTests, IntegrationTests):