mirror of
https://github.com/django/django.git
synced 2025-06-03 02:29:13 +00:00
Fixed #36281 -- Used async-safe write in ASGIHandler.read_body().
Thanks Carlton Gibson for reviews.
This commit is contained in:
parent
9d93e35c20
commit
1fb3f57e81
@ -263,7 +263,16 @@ class ASGIHandler(base.BaseHandler):
|
|||||||
raise RequestAborted()
|
raise RequestAborted()
|
||||||
# Add a body chunk from the message, if provided.
|
# Add a body chunk from the message, if provided.
|
||||||
if "body" in message:
|
if "body" in message:
|
||||||
body_file.write(message["body"])
|
on_disk = getattr(body_file, "_rolled", False)
|
||||||
|
if on_disk:
|
||||||
|
async_write = sync_to_async(
|
||||||
|
body_file.write,
|
||||||
|
thread_sensitive=False,
|
||||||
|
)
|
||||||
|
await async_write(message["body"])
|
||||||
|
else:
|
||||||
|
body_file.write(message["body"])
|
||||||
|
|
||||||
# Quit out if that's the end.
|
# Quit out if that's the end.
|
||||||
if not message.get("more_body", False):
|
if not message.get("more_body", False):
|
||||||
break
|
break
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from asgiref.testing import ApplicationCommunicator
|
from asgiref.testing import ApplicationCommunicator
|
||||||
@ -659,3 +661,64 @@ class ASGITest(SimpleTestCase):
|
|||||||
# 'last\n' isn't sent.
|
# 'last\n' isn't sent.
|
||||||
with self.assertRaises(asyncio.TimeoutError):
|
with self.assertRaises(asyncio.TimeoutError):
|
||||||
await communicator.receive_output(timeout=0.2)
|
await communicator.receive_output(timeout=0.2)
|
||||||
|
|
||||||
|
async def test_read_body_thread(self):
|
||||||
|
"""Write runs on correct thread depending on rollover."""
|
||||||
|
handler = ASGIHandler()
|
||||||
|
loop_thread = threading.current_thread()
|
||||||
|
|
||||||
|
called_threads = []
|
||||||
|
|
||||||
|
def write_wrapper(data):
|
||||||
|
called_threads.append(threading.current_thread())
|
||||||
|
return original_write(data)
|
||||||
|
|
||||||
|
# In-memory write (no rollover expected).
|
||||||
|
in_memory_chunks = [
|
||||||
|
{"type": "http.request", "body": b"small", "more_body": False}
|
||||||
|
]
|
||||||
|
|
||||||
|
async def receive():
|
||||||
|
return in_memory_chunks.pop(0)
|
||||||
|
|
||||||
|
with tempfile.SpooledTemporaryFile(max_size=1024, mode="w+b") as temp_file:
|
||||||
|
original_write = temp_file.write
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"django.core.handlers.asgi.tempfile.SpooledTemporaryFile",
|
||||||
|
return_value=temp_file,
|
||||||
|
),
|
||||||
|
patch.object(temp_file, "write", side_effect=write_wrapper),
|
||||||
|
):
|
||||||
|
await handler.read_body(receive)
|
||||||
|
# Write was called in the event loop thread.
|
||||||
|
self.assertIn(loop_thread, called_threads)
|
||||||
|
|
||||||
|
# Clear thread log before next test.
|
||||||
|
called_threads.clear()
|
||||||
|
|
||||||
|
# Rollover to disk (write should occur in a threadpool thread).
|
||||||
|
rolled_chunks = [
|
||||||
|
{"type": "http.request", "body": b"A" * 16, "more_body": True},
|
||||||
|
{"type": "http.request", "body": b"B" * 16, "more_body": False},
|
||||||
|
]
|
||||||
|
|
||||||
|
async def receive_rolled():
|
||||||
|
return rolled_chunks.pop(0)
|
||||||
|
|
||||||
|
with (
|
||||||
|
override_settings(FILE_UPLOAD_MAX_MEMORY_SIZE=10),
|
||||||
|
tempfile.SpooledTemporaryFile(max_size=10, mode="w+b") as temp_file,
|
||||||
|
):
|
||||||
|
original_write = temp_file.write
|
||||||
|
# roll_over force in handlers.
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"django.core.handlers.asgi.tempfile.SpooledTemporaryFile",
|
||||||
|
return_value=temp_file,
|
||||||
|
),
|
||||||
|
patch.object(temp_file, "write", side_effect=write_wrapper),
|
||||||
|
):
|
||||||
|
await handler.read_body(receive_rolled)
|
||||||
|
# The second write should have rolled over to disk.
|
||||||
|
self.assertTrue(any(t != loop_thread for t in called_threads))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user