Skip to content

Commit c42e93c

Browse files
Make sure SlicedFile is closed properly (#612)
Fixes #556 --------- Co-authored-by: Adam Johnson <[email protected]>
1 parent f8dff50 commit c42e93c

File tree

3 files changed

+47
-0
lines changed

3 files changed

+47
-0
lines changed

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Unreleased
1111

1212
* Support Python 3.13.
1313

14+
* Fix a bug introduced in version 6.0.0 where ``Range`` requests could lead to database connection errors in other requests.
15+
16+
Thanks to Per Myren for the detailed investigation and fix in `PR #612 <https://github.com/evansd/whitenoise/pull/612>`__.
17+
1418
6.7.0 (2024-06-19)
1519
------------------
1620

src/whitenoise/responders.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def read(self, size=-1):
6464
return data
6565

6666
def close(self):
67+
super().close()
6768
self.fileobj.close()
6869

6970

tests/test_responders.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from __future__ import annotations
2+
3+
from io import BytesIO
4+
5+
from django.test import SimpleTestCase
6+
7+
from whitenoise.responders import SlicedFile
8+
9+
10+
class SlicedFileTests(SimpleTestCase):
11+
def test_close_does_not_rerun_on_del(self):
12+
"""
13+
Regression test for the subtle close() behaviour of SlicedFile that
14+
could lead to database connection errors.
15+
16+
https://github.com/evansd/whitenoise/pull/612
17+
"""
18+
file = BytesIO(b"1234567890")
19+
sliced_file = SlicedFile(file, 1, 2)
20+
21+
# Emulate how Django patches the file object's close() method to be
22+
# response.close() and count the calls.
23+
# https://github.com/django/django/blob/345a6652e6a15febbf4f68351dcea5dd674ea324/django/core/handlers/wsgi.py#L137-L140
24+
calls = 0
25+
26+
file_close = sliced_file.close
27+
28+
def closer():
29+
nonlocal calls, file_close
30+
calls += 1
31+
if file_close is not None:
32+
file_close()
33+
file_close = None
34+
35+
sliced_file.close = closer
36+
37+
sliced_file.close()
38+
assert calls == 1
39+
40+
# Deleting the sliced file should not call close again.
41+
del sliced_file
42+
assert calls == 1

0 commit comments

Comments
 (0)