diff --git a/docs/changelog.rst b/docs/changelog.rst index b29ef4cf..8f065377 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,10 @@ Unreleased * Support Python 3.13. +* Fix a bug introduced in version 6.0.0 where ``Range`` requests could lead to database connection errors in other requests. + + Thanks to Per Myren for the detailed investigation and fix in `PR #612 `__. + 6.7.0 (2024-06-19) ------------------ diff --git a/src/whitenoise/responders.py b/src/whitenoise/responders.py index 9501ea65..f85fe0e7 100644 --- a/src/whitenoise/responders.py +++ b/src/whitenoise/responders.py @@ -64,6 +64,7 @@ def read(self, size=-1): return data def close(self): + super().close() self.fileobj.close() diff --git a/tests/test_responders.py b/tests/test_responders.py new file mode 100644 index 00000000..8bc7b70e --- /dev/null +++ b/tests/test_responders.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from io import BytesIO + +from django.test import SimpleTestCase + +from whitenoise.responders import SlicedFile + + +class SlicedFileTests(SimpleTestCase): + def test_close_does_not_rerun_on_del(self): + """ + Regression test for the subtle close() behaviour of SlicedFile that + could lead to database connection errors. + + https://github.com/evansd/whitenoise/pull/612 + """ + file = BytesIO(b"1234567890") + sliced_file = SlicedFile(file, 1, 2) + + # Emulate how Django patches the file object's close() method to be + # response.close() and count the calls. + # https://github.com/django/django/blob/345a6652e6a15febbf4f68351dcea5dd674ea324/django/core/handlers/wsgi.py#L137-L140 + calls = 0 + + file_close = sliced_file.close + + def closer(): + nonlocal calls, file_close + calls += 1 + if file_close is not None: + file_close() + file_close = None + + sliced_file.close = closer + + sliced_file.close() + assert calls == 1 + + # Deleting the sliced file should not call close again. + del sliced_file + assert calls == 1