Skip to content

Commit

Permalink
Use loop.sendfile() instead of custom implementation if available (#4517
Browse files Browse the repository at this point in the history
)
  • Loading branch information
asvetlov committed Oct 16, 2020
1 parent c40941c commit 41e0637
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGES/4269.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use ``loop.sendfile()`` instead of custom implementation if available.
38 changes: 32 additions & 6 deletions aiohttp/web_fileresponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ def __init__(self,
protocol: BaseProtocol,
loop: asyncio.AbstractEventLoop,
fobj: IO[Any],
offset: int,
count: int,
on_chunk_sent: _T_OnChunkSent=None) -> None:
super().__init__(protocol, loop, on_chunk_sent)
self._sendfile_buffer = [] # type: List[bytes]
self._fobj = fobj
self._count = count
self._offset = fobj.tell()
self._offset = offset
self._in_fd = fobj.fileno()

def _write(self, chunk: bytes) -> None:
Expand Down Expand Up @@ -94,12 +95,25 @@ def _done_fut(self, out_fd: int, fut: 'asyncio.Future[None]') -> None:

async def sendfile(self) -> None:
assert self.transport is not None
loop = self.loop
data = b''.join(self._sendfile_buffer)
if hasattr(loop, "sendfile"):
# Python 3.7+
self.transport.write(data)
await loop.sendfile(
self.transport,
self._fobj,
self._offset,
self._count
)
await super().write_eof()
return

self._fobj.seek(self._offset)
out_socket = self.transport.get_extra_info('socket').dup()
out_socket.setblocking(False)
out_fd = out_socket.fileno()

loop = self.loop
data = b''.join(self._sendfile_buffer)
try:
await loop.sock_sendall(out_socket, data)
if not self._do_sendfile(out_fd):
Expand Down Expand Up @@ -139,6 +153,7 @@ def __init__(self, path: Union[str, pathlib.Path],

async def _sendfile_system(self, request: 'BaseRequest',
fobj: IO[Any],
offset: int,
count: int) -> AbstractStreamWriter:
# Write count bytes of fobj to resp using
# the os.sendfile system call.
Expand All @@ -156,12 +171,18 @@ async def _sendfile_system(self, request: 'BaseRequest',
if (transport.get_extra_info("sslcontext") or
transport.get_extra_info("socket") is None or
self.compression):
writer = await self._sendfile_fallback(request, fobj, count)
writer = await self._sendfile_fallback(
request,
fobj,
offset,
count
)
else:
writer = SendfileStreamWriter(
request.protocol,
request._loop,
fobj,
offset,
count
)
request._payload_writer = writer
Expand All @@ -173,6 +194,7 @@ async def _sendfile_system(self, request: 'BaseRequest',

async def _sendfile_fallback(self, request: 'BaseRequest',
fobj: IO[Any],
offset: int,
count: int) -> AbstractStreamWriter:
# Mimic the _sendfile_system() method, but without using the
# os.sendfile() system call. This should be used on systems
Expand All @@ -187,6 +209,8 @@ async def _sendfile_fallback(self, request: 'BaseRequest',
chunk_size = self._chunk_size
loop = asyncio.get_event_loop()

await loop.run_in_executor(None, fobj.seek, offset)

chunk = await loop.run_in_executor(None, fobj.read, chunk_size)
while chunk:
await writer.write(chunk)
Expand Down Expand Up @@ -338,9 +362,11 @@ async def prepare(

fobj = await loop.run_in_executor(None, filepath.open, 'rb')
if start: # be aware that start could be None or int=0 here.
await loop.run_in_executor(None, fobj.seek, start)
offset = start
else:
offset = 0

try:
return await self._sendfile(request, fobj, count)
return await self._sendfile(request, fobj, offset, count)
finally:
await loop.run_in_executor(None, fobj.close)
2 changes: 1 addition & 1 deletion tests/test_web_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ async def test_close(srv, transport) -> None:
b'Host: example.com\r\n'
b'Content-Length: 0\r\n\r\n')

await asyncio.sleep(0.05)
await asyncio.sleep(0.1)
assert srv._task_handler
assert srv._waiter

Expand Down

0 comments on commit 41e0637

Please sign in to comment.