diff options
author | Andrew Svetlov <andrew.svetlov@gmail.com> | 2018-01-16 19:59:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-16 19:59:34 +0200 |
commit | 6b5a27975a415108a5eac12ee302bf2b3233f4d4 (patch) | |
tree | 09e3233c5c9c9b269c5cc47a0ed97a151280daac /Lib/asyncio/unix_events.py | |
parent | c495e799ed376af91ae2ddf6c4bcc592490fe294 (diff) | |
download | cpython-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.py | 93 |
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): |