Skip to content
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

Merged
merged 54 commits into from
Jan 16, 2018

Conversation

asvetlov
Copy link
Contributor

@asvetlov asvetlov commented Dec 22, 2017

@asvetlov
Copy link
Contributor Author

asvetlov commented Dec 22, 2017

A draft, needs more tests and docs update.

@asvetlov
Copy link
Contributor Author

@1st1 please take a look.

I've copied many code lines for pre-checks from socket.sendfile() implementation.
If you like the idea I want extract these lines from socket.sendfile into private funcs in socket module and reuse it by asyncio.

Otherwise I should duplicate not only implementation but tests too, I'd like to avoid it.

@@ -20,6 +20,10 @@
from . import format_helpers


class SendfileUnsupportedError(ValueError):
Copy link
Member

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')?

os.sendfile
except AttributeError as exc:
raise events.SendfileUnsupportedError(exc)
self._check_sendfile_params(sock, file, offset, count)
Copy link
Member

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.

try:
fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as exc:
raise events.SendfileUnsupportedError(exc) # not a regular file
Copy link
Member

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`

try:
fsize = os.fstat(fileno).st_size
except OSError as exc:
raise events.SendfileUnsupportedError(exc) # not a regular file
Copy link
Member

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

@1st1
Copy link
Member

1st1 commented Dec 23, 2017

Did some initial review.

try:
os.sendfile
except AttributeError as exc:
raise NotImplementedError("os.sendfile() in not available")
Copy link
Member

@1st1 1st1 Dec 24, 2017

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?

# 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)
Copy link
Member

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

Copy link
Member

@1st1 1st1 left a 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.

return await self._sock_sendfile_fallback(sock, file,
offset, count)
else:
raise
Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Member

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.

@asvetlov
Copy link
Contributor Author

asvetlov commented Jan 2, 2018

Two things a left:

  1. Full coverage (need a couple mocked tests)
  2. Unregistering fd on cancellation.

@asvetlov
Copy link
Contributor Author

asvetlov commented Jan 2, 2018

Done.
Everything is test covered.
Implementation raises a private _SendfileNotAvailable but it is converted into RuntimeError with proper message text when goes to user (fallback=False).

Copy link
Member

@1st1 1st1 left a 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?

raise RuntimeError(exc.args[0]) from None

async def _sock_sendfile_native(self, sock, file, offset, count):
raise _SendfileNotAvailable("Fast sendfile is not available")
Copy link
Member

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'

Copy link
Member

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():
Copy link
Member

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?

@1st1
Copy link
Member

1st1 commented Jan 15, 2018

@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?

@asvetlov
Copy link
Contributor Author

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:
Copy link
Member

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.

@1st1
Copy link
Member

1st1 commented Jan 16, 2018

I hope the PR is ready now

The fallback logic is messed up.

@asvetlov
Copy link
Contributor Author

Sorry. Fixed.

Copy link
Member

@1st1 1st1 left a 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.

@asvetlov
Copy link
Contributor Author

Thanks for careful review!

@asvetlov asvetlov merged commit 6b5a279 into python:master Jan 16, 2018
@asvetlov asvetlov deleted the sock_sendfile branch January 16, 2018 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants