summaryrefslogtreecommitdiff
path: root/Lib/asyncio/unix_events.py
diff options
context:
space:
mode:
authorAndrew Svetlov <andrew.svetlov@gmail.com>2018-01-16 19:59:34 +0200
committerGitHub <noreply@github.com>2018-01-16 19:59:34 +0200
commit6b5a27975a415108a5eac12ee302bf2b3233f4d4 (patch)
tree09e3233c5c9c9b269c5cc47a0ed97a151280daac /Lib/asyncio/unix_events.py
parentc495e799ed376af91ae2ddf6c4bcc592490fe294 (diff)
downloadcpython-git-6b5a27975a415108a5eac12ee302bf2b3233f4d4.tar.gz
bpo-32410: Implement loop.sock_sendfile() (#4976)
Diffstat (limited to 'Lib/asyncio/unix_events.py')
-rw-r--r--Lib/asyncio/unix_events.py93
1 files changed, 93 insertions, 0 deletions
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index 4f6beb4365..f40ef12f26 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -1,6 +1,7 @@
"""Selector event loop for Unix with signal handling."""
import errno
+import io
import os
import selectors
import signal
@@ -308,6 +309,98 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
ssl_handshake_timeout=ssl_handshake_timeout)
return server
+ async def _sock_sendfile_native(self, sock, file, offset, count):
+ try:
+ os.sendfile
+ except AttributeError as exc:
+ raise base_events._SendfileNotAvailable(
+ "os.sendfile() is not available")
+ try:
+ fileno = file.fileno()
+ except (AttributeError, io.UnsupportedOperation) as err:
+ raise base_events._SendfileNotAvailable("not a regular file")
+ try:
+ fsize = os.fstat(fileno).st_size
+ except OSError as err:
+ raise base_events._SendfileNotAvailable("not a regular file")
+ blocksize = count if count else fsize
+ if not blocksize:
+ return 0 # empty file
+
+ fut = self.create_future()
+ self._sock_sendfile_native_impl(fut, None, sock, fileno,
+ offset, count, blocksize, 0)
+ return await fut
+
+ def _sock_sendfile_native_impl(self, fut, registered_fd, sock, fileno,
+ offset, count, blocksize, total_sent):
+ fd = sock.fileno()
+ if registered_fd is not None:
+ # Remove the callback early. It should be rare that the
+ # selector says the fd is ready but the call still returns
+ # EAGAIN, and I am willing to take a hit in that case in
+ # order to simplify the common case.
+ self.remove_writer(registered_fd)
+ if fut.cancelled():
+ self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ return
+ if count:
+ blocksize = count - total_sent
+ if blocksize <= 0:
+ self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ fut.set_result(total_sent)
+ return
+
+ try:
+ sent = os.sendfile(fd, fileno, offset, blocksize)
+ except (BlockingIOError, InterruptedError):
+ if registered_fd is None:
+ self._sock_add_cancellation_callback(fut, sock)
+ self.add_writer(fd, self._sock_sendfile_native_impl, fut,
+ fd, sock, fileno,
+ offset, count, blocksize, total_sent)
+ except OSError as exc:
+ if total_sent == 0:
+ # We can get here for different reasons, the main
+ # one being 'file' is not a regular mmap(2)-like
+ # file, in which case we'll fall back on using
+ # plain send().
+ err = base_events._SendfileNotAvailable(
+ "os.sendfile call failed")
+ self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ fut.set_exception(err)
+ else:
+ self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ fut.set_exception(exc)
+ except Exception as exc:
+ self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ fut.set_exception(exc)
+ else:
+ if sent == 0:
+ # EOF
+ self._sock_sendfile_update_filepos(fileno, offset, total_sent)
+ fut.set_result(total_sent)
+ else:
+ offset += sent
+ total_sent += sent
+ if registered_fd is None:
+ self._sock_add_cancellation_callback(fut, sock)
+ self.add_writer(fd, self._sock_sendfile_native_impl, fut,
+ fd, sock, fileno,
+ offset, count, blocksize, total_sent)
+
+ def _sock_sendfile_update_filepos(self, fileno, offset, total_sent):
+ if total_sent > 0:
+ os.lseek(fileno, offset, os.SEEK_SET)
+
+ def _sock_add_cancellation_callback(self, fut, sock):
+ def cb(fut):
+ if fut.cancelled():
+ fd = sock.fileno()
+ if fd != -1:
+ self.remove_writer(fd)
+ fut.add_done_callback(cb)
+
class _UnixReadPipeTransport(transports.ReadTransport):