bpo-32410: Implement loop.sock_sendfile()#4976
Conversation
|
A draft, needs more tests and docs update. |
|
@1st1 please take a look. I've copied many code lines for pre-checks from Otherwise I should duplicate not only implementation but tests too, I'd like to avoid it. |
| from . import format_helpers | ||
|
|
||
|
|
||
| class SendfileUnsupportedError(ValueError): |
There was a problem hiding this comment.
SendFileNotSupported
Why not raise NotImplementedError('sendfile is not available')?
| os.sendfile | ||
| except AttributeError as exc: | ||
| raise events.SendfileUnsupportedError(exc) | ||
| self._check_sendfile_params(sock, file, offset, count) |
There was a problem hiding this comment.
We should support passing a FD for file.
| try: | ||
| fileno = file.fileno() | ||
| except (AttributeError, io.UnsupportedOperation) as exc: | ||
| raise events.SendfileUnsupportedError(exc) # not a regular file |
There was a problem hiding this comment.
I think this is redundant. I'd do something like this:
if isinstance(file, int):
fileno = file
else:
# factor out the 'fileno()' getting part from `_ensure_fd_no_transport`| try: | ||
| fsize = os.fstat(fileno).st_size | ||
| except OSError as exc: | ||
| raise events.SendfileUnsupportedError(exc) # not a regular file |
There was a problem hiding this comment.
raise events.SendfileUnsupportedError('fstat call failed') from exc
|
Did some initial review. |
| try: | ||
| os.sendfile | ||
| except AttributeError as exc: | ||
| raise NotImplementedError("os.sendfile() in not available") |
There was a problem hiding this comment.
Actually, I think you were right initially, Andrew, to not use the NotImplementedError. I think it means "not implemented in Python code", and shouldn't be used for signalling about missing platform capabilities. On the other hand, adding a new exception type just for sendfile doesn't feel right either. How about we simply raise a RuntimeError("os.sendfile() in not available") here, what do you think?
| # one being 'file' is not a regular mmap(2)-like | ||
| # file, in which case we'll fall back on using | ||
| # plain send(). | ||
| err = NotImplementedError(exc) |
There was a problem hiding this comment.
I'd say raise RuntimeError('os.sendfile() call failed') from exc
1st1
left a comment
There was a problem hiding this comment.
Please wrap the private exception that we use to signal that there's no native sendfile into a RuntimeError. LGTM otherwise.
| return await self._sock_sendfile_fallback(sock, file, | ||
| offset, count) | ||
| else: | ||
| raise |
There was a problem hiding this comment.
raise RuntimeError('os.sendfile is not available') from None
There was a problem hiding this comment.
Proposed message text is not always correct: sometimes os.sendfile is available but passed file object cannot be used by syscall.
_SendfileNotAvailable is derived from RuntimeError now and it always contains proper message text.
The best what I can do is reraising:
raise RuntimeError(exc.args[0]) from None
There was a problem hiding this comment.
In this case let's make the exception public. I'd rename it to SendfileNotAvailableError and export it to asyncio module. Don't wrap it then.
|
Two things a left:
|
|
Done. |
1st1
left a comment
There was a problem hiding this comment.
Almost there. My major question is why we need tribool for fallback? IMO it complicates the API and isnt going to be used much, doesn't it?
| raise RuntimeError(exc.args[0]) from None | ||
|
|
||
| async def _sock_sendfile_native(self, sock, file, offset, count): | ||
| raise _SendfileNotAvailable("Fast sendfile is not available") |
There was a problem hiding this comment.
i'd change to 'sendfile syscall is not available'
There was a problem hiding this comment.
or to 'os.sendfile is not available'
'fast sendfile' is rather cryptic and doesn't really tell anything to the user
| # 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(): |
|
@asvetlov This PR is almost there, there are only a few nits left (and tribool commit reversal). Let's merge this in and work on sendfile for transports? |
|
I hope the PR is ready now |
| if self._debug and sock.gettimeout() != 0: | ||
| raise ValueError("the socket must be non-blocking") | ||
| self._check_sendfile_params(sock, file, offset, count) | ||
| if fallback: |
There was a problem hiding this comment.
The logic must be different.
Currently you use slow fallback code if fallback=True.
Instead you should always try to use fast syscall first, and if it fails, we check if the fallback is enabled. If it is, we emulate sendfile, if not -- we raise an error.
The fallback logic is messed up. |
|
Sorry. Fixed. |
1st1
left a comment
There was a problem hiding this comment.
I think it's in a good shape now, thanks Andrew. LGTM.
|
Thanks for careful review! |
https://bugs.python.org/issue32410