-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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. |
Lib/asyncio/events.py
Outdated
@@ -20,6 +20,10 @@ | |||
from . import format_helpers | |||
|
|||
|
|||
class SendfileUnsupportedError(ValueError): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SendFileNotSupported
Why not raise NotImplementedError('sendfile is not available')
?
Lib/asyncio/unix_events.py
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should support passing a FD for file
.
Lib/asyncio/unix_events.py
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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`
Lib/asyncio/unix_events.py
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raise events.SendfileUnsupportedError('fstat call failed') from exc
Did some initial review. |
Lib/asyncio/unix_events.py
Outdated
try: | ||
os.sendfile | ||
except AttributeError as exc: | ||
raise NotImplementedError("os.sendfile() in not available") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
Lib/asyncio/unix_events.py
Outdated
# 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say raise RuntimeError('os.sendfile() call failed') from exc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please wrap the private exception that we use to signal that there's no native sendfile into a RuntimeError
. LGTM otherwise.
Lib/asyncio/base_events.py
Outdated
return await self._sock_sendfile_fallback(sock, file, | ||
offset, count) | ||
else: | ||
raise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raise RuntimeError('os.sendfile is not available') from None
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
Lib/asyncio/base_events.py
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd change to 'sendfile syscall is not available'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we still need this check?
@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 |
Lib/asyncio/base_events.py
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's in a good shape now, thanks Andrew. LGTM.
Thanks for careful review! |
https://bugs.python.org/issue32410