summaryrefslogtreecommitdiffstats
path: root/Lib/asyncio/base_events.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/asyncio/base_events.py')
-rw-r--r--Lib/asyncio/base_events.py70
1 files changed, 70 insertions, 0 deletions
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index ab00231..b6a9384 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -154,6 +154,10 @@ def _run_until_complete_cb(fut):
futures._get_loop(fut).stop()
+class _SendfileNotAvailable(RuntimeError):
+ pass
+
+
class Server(events.AbstractServer):
def __init__(self, loop, sockets):
@@ -647,6 +651,72 @@ class BaseEventLoop(events.AbstractEventLoop):
return await self.run_in_executor(
None, socket.getnameinfo, sockaddr, flags)
+ async def sock_sendfile(self, sock, file, offset=0, count=None,
+ *, fallback=True):
+ if self._debug and sock.gettimeout() != 0:
+ raise ValueError("the socket must be non-blocking")
+ self._check_sendfile_params(sock, file, offset, count)
+ try:
+ return await self._sock_sendfile_native(sock, file,
+ offset, count)
+ except _SendfileNotAvailable as exc:
+ if fallback:
+ return await self._sock_sendfile_fallback(sock, file,
+ offset, count)
+ else:
+ raise RuntimeError(exc.args[0]) from None
+
+ async def _sock_sendfile_native(self, sock, file, offset, count):
+ # NB: sendfile syscall is not supported for SSL sockets and
+ # non-mmap files even if sendfile is supported by OS
+ raise _SendfileNotAvailable(
+ f"syscall sendfile is not available for socket {sock!r} "
+ "and file {file!r} combination")
+
+ async def _sock_sendfile_fallback(self, sock, file, offset, count):
+ if offset:
+ file.seek(offset)
+ blocksize = min(count, 16384) if count else 16384
+ buf = bytearray(blocksize)
+ total_sent = 0
+ try:
+ while True:
+ if count:
+ blocksize = min(count - total_sent, blocksize)
+ if blocksize <= 0:
+ break
+ view = memoryview(buf)[:blocksize]
+ read = file.readinto(view)
+ if not read:
+ break # EOF
+ await self.sock_sendall(sock, view)
+ total_sent += read
+ return total_sent
+ finally:
+ if total_sent > 0 and hasattr(file, 'seek'):
+ file.seek(offset + total_sent)
+
+ def _check_sendfile_params(self, sock, file, offset, count):
+ if 'b' not in getattr(file, 'mode', 'b'):
+ raise ValueError("file should be opened in binary mode")
+ if not sock.type == socket.SOCK_STREAM:
+ raise ValueError("only SOCK_STREAM type sockets are supported")
+ if count is not None:
+ if not isinstance(count, int):
+ raise TypeError(
+ "count must be a positive integer (got {!r})".format(count))
+ if count <= 0:
+ raise ValueError(
+ "count must be a positive integer (got {!r})".format(count))
+ if not isinstance(offset, int):
+ raise TypeError(
+ "offset must be a non-negative integer (got {!r})".format(
+ offset))
+ if offset < 0:
+ raise ValueError(
+ "offset must be a non-negative integer (got {!r})".format(
+ offset))
+
async def create_connection(
self, protocol_factory, host=None, port=None,
*, ssl=None, family=0,