-
-
Notifications
You must be signed in to change notification settings - Fork 178
Make loop TCP APIs accept only TCP streaming sockets #453
Conversation
if (sys.platform == 'win32' and | ||
isinstance(self.loop, proactor_events.BaseProactorEventLoop)): | ||
raise unittest.SkipTest( | ||
'UCP not supported with proactor event loops') |
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.
Typo, UDP?
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.
Fixed
@@ -1394,7 +1399,7 @@ def test_create_datagram_endpoint_sock(self): | |||
else: | |||
assert False, 'Can not create socket.' | |||
|
|||
f = self.loop.create_connection( | |||
f = self.loop.create_datagram_endpoint( |
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'm worried here that this test doesn't test much...
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.
Yeah, you're right, it tests only that the method doesn't crash. Still it was testing a wrong method.
b04c696
to
2abb370
Compare
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.
(OT: connect_accepted_socket seems undocumented.)
@@ -1027,6 +1031,9 @@ def create_server(self, protocol_factory, host=None, port=None, | |||
else: | |||
if sock is None: | |||
raise ValueError('Neither host/port nor sock were specified') | |||
if sock.family not in {socket.AF_INET, socket.AF_INET6}: |
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.
Careful, check line 972/976. AF_INET6 may not always exist in the socket module (apparently).
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.
Good point. Will fix this.
@@ -1048,6 +1055,13 @@ def connect_accepted_socket(self, protocol_factory, sock, *, ssl=None): | |||
This method is a coroutine. When completed, the coroutine | |||
returns a (transport, protocol) pair. | |||
""" | |||
socket_families = {socket.AF_INET, socket.AF_INET6} | |||
if hasattr(socket, 'AF_UNIX'): | |||
socket_families.add(socket.AF_UNIX) |
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.
If a UNIX socket is fine here, why not in create_connection()? They both end up calling _create_connection_transport()
.
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.
Yes, in asyncio both create_connection
and connect_accepted_socket
use similar internal API. It's not the same in uvloop.
The problem is that we have create_connection
and create_unix_connection
, that are designed for TCP and UNIX sockets. uvloop implements this spec, using appropriate low-level libuv primitives for each case. It doesn't make a lot of sense to add "if" checks to accept Unix sockets in create_connection
(as we already have dedicated APIs for that).
connect_accepted_socket
on the other hand doesn't specify the kind of socket it accepts. And it doesn't make a lot of sense to add connect_accepted_unix_socket
.
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.
So maybe connect_accepted_socket() should only check whether it's a stream socket, not whether it's any particular address family?
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.
That means that users will be able to pass AF_BLUETOOTH
sockets or something. Those aren't supported in uvloop, and I'm not sure that even asyncio supports them properly.
In any case, I think you're right, let's only check for SOCK_STREAM
here. I'll also add SOCK_STREAM
checks in create_connection
, create_server
, as they simply won't work with anything else. AF_INET6
has to be properly handled too. Let me update the PR.
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 have some additional worry now. You've mentioned twice (I think) "but uvloop doesn't support that". I don't think that's a valid argument. (Haven't we discussed this before?) I think it's fine if uvloop doesn't support e.g. bluetooth sockets but pure-Python asyncio does. That should be documented with uvloop; but I don't think "uvloop can't do this" is enough to conclude "hence asyncio shouldn't do this either". (Especially since I remember seeing a bug about code that would crash when passing a bluetooth address; IIRC it was a getaddrinfo() issue.)
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.
Re uvloop - I agree. The updated PR only makes sure that:
create_datagram_endpoint
only acceptsSOCK_DGRAM
sockets (it doesn't make sense to accept anything else).create_connection
andcreate_server
only acceptAF_INET
,AF_INET6
andSOCK_STREAM
sockets. We have separate asyncio API forAF_UNIX
sockets.connect_accepted_socket
only acceptsSOCK_STREAM
sockets (no restrictions on family, eventAF_BLUETOOTH
will be accepted just fine. Even though uvloop won't work with the latter).
All in all, this PR is not about uvloop, it only ensures that asyncio APIs are used correctly. It doesn't limit asyncio in any way.
I've committed Jim's documentation patch: http://bugs.python.org/issue27392. I've also updated this PR to address your comments. |
Thanks, LGTM now! |
fb40156
to
2608734
Compare
I've updated the patch with more unittests and more robust checks of |
_SOCKET_TYPE_MASK |= socket.SOCK_NONBLOCK | ||
if hasattr(socket, 'SOCK_CLOEXEC'): | ||
_SOCKET_TYPE_MASK |= socket.SOCK_CLOEXEC | ||
def _is_stream_socket(sock_type): |
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 would rename this to _is_stream_socket_type()
, and similar for the following functions (using _family
as the suffix for _is_ip_socket
) to emphasize which field of the socket is supposed to be passed into these functions.
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.
Since _is_ip_socket
no longer uses those methods, I refactored is_stream_socket
to accept socket objects directly, which made the code simpler overall. See the update.
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.
Even better!
More specifically: * loop.create_connection() and loop.create_server() can accept AF_INET or AF_INET6 SOCK_STREAM sockets; * loop.create_datagram_endpoint() can accept only SOCK_DGRAM sockets; * loop.connect_accepted_socket() can accept only SOCK_STREAM sockets; * fixed a bug in create_unix_server() and create_unix_connection() to properly check for SOCK_STREAM sockets on Linux; * fixed static DNS resolution to decline socket types that aren't strictly equal to SOCK_STREAM or SOCK_DGRAM. On Linux socket type can be a bit mask, and we should let system getaddrinfo() to deal with it.
Thanks for the review, Guido. I've implemented the same set of features in uvloop, and will watch closely if they cause any problems (I highly doubt that). |
Whee!
|
I rolled back the restriction of passing |
Currently,
loop.create_server
,loop.create_connection
, andloop.connect_accepted_socket
accept any kind of socket, although they are documented (and used by asyncio users) as TCP API.Allowing those methods to accept any kinds of sockets leads to hard to debug bugs. For instance, aiohttp gunicorn worker simply passes all sockets to
create_server
(including UNIX sockets). We even have a unittest in asyncio that does that:test_create_datagram_endpoint_sock
passes a UDP socket tocreate_connection
.TCP, UDP and UNIX sockets are different from each other and have some subtle API differences. We shouldn't allow to blindly pass any of them to
create_server
and other TCP APIs.