From c77c0588a8cec3d46990ecbca6fd6b169217701e Mon Sep 17 00:00:00 2001 From: Evgeny Tolmachev Date: Wed, 2 Jan 2019 22:45:25 +0300 Subject: [PATCH] FileResponse works with files asynchronously (#3476) * FileResponse works with files asynchronously * FileResponse: the 'prepare' method works with files asynchronously --- CHANGES/3313.feature | 1 + CONTRIBUTORS.txt | 1 + aiohttp/web_fileresponse.py | 19 +++++++++++-------- 3 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 CHANGES/3313.feature diff --git a/CHANGES/3313.feature b/CHANGES/3313.feature new file mode 100644 index 00000000000..d19478bbcc0 --- /dev/null +++ b/CHANGES/3313.feature @@ -0,0 +1 @@ +FileResponse from web_fileresponse.py uses a ThreadPoolExecutor to work with files asynchronously. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 5cfd24d5a28..1188ca1ccf1 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -82,6 +82,7 @@ Eric Sheng Erich Healy Eugene Chernyshov Eugene Naydenov +Eugene Tolmachev Evert Lammerts FichteFoll Frederik Gladhorn diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index ffa610ea203..97c4205db83 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -179,22 +179,24 @@ async def _sendfile_fallback(self, request: 'BaseRequest', # os.sendfile() system call. This should be used on systems # that don't support the os.sendfile(). - # To avoid blocking the event loop & to keep memory usage low, - # fobj is transferred in chunks controlled by the - # constructor's chunk_size argument. + # To keep memory usage low,fobj is transferred in chunks + # controlled by the constructor's chunk_size argument. writer = await super().prepare(request) assert writer is not None chunk_size = self._chunk_size + loop = asyncio.get_event_loop() - chunk = fobj.read(chunk_size) + chunk = await loop.run_in_executor(None, fobj.read, chunk_size) while chunk: await writer.write(chunk) count = count - chunk_size if count <= 0: break - chunk = fobj.read(min(chunk_size, count)) + chunk = await loop.run_in_executor( + None, fobj.read, min(chunk_size, count) + ) await writer.drain() return writer @@ -218,7 +220,8 @@ async def prepare( filepath = gzip_path gzip = True - st = filepath.stat() + loop = asyncio.get_event_loop() + st = await loop.run_in_executor(None, filepath.stat) modsince = request.if_modified_since if modsince is not None and st.st_mtime <= modsince.timestamp(): @@ -334,8 +337,8 @@ async def prepare( self.headers[hdrs.CONTENT_RANGE] = 'bytes {0}-{1}/{2}'.format( real_start, real_start + count - 1, file_size) - with filepath.open('rb') as fobj: + with (await loop.run_in_executor(None, filepath.open, 'rb')) as fobj: if start: # be aware that start could be None or int=0 here. - fobj.seek(start) + await loop.run_in_executor(None, fobj.seek, start) return await self._sendfile(request, fobj, count)