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

Amend RFC 517: Add material on std::net #807

Merged
merged 3 commits into from
Feb 18, 2015
Merged

Conversation

alexcrichton
Copy link
Member

The IO reform RFC is being split into several semi-independent pieces, posted as PRs like this one.

This RFC amendment adds the networking section.

Rendered

@alexcrichton alexcrichton self-assigned this Feb 3, 2015
* The static distinction between `TcpAcceptor` and `TcpListener` has been
removed (more on this in the [socket][Sockets] section).
* The `clone` functionality has been removed in favor of `duplicate` (same
caveats as `TcpStream`).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning for not using clone here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Clone trait primarily indicates that the clone operation itself is infallible whereas the system call dup can fail (file descriptor exhaustion). I suspect that WSADuplicateSocket may also have some similar error conditions.

@kjpgit
Copy link

kjpgit commented Feb 3, 2015

I had to look in the source code to see that SO_REUSEADDR was being set. It's useful, but should be documented and have an option. Also, SO_REUSEPORT (a newish option for linux/bsd) would be really, really nice to support as an option http://lwn.net/Articles/542629/

I have a threaded server that does concurrent accepts, on the same 'cloned' socket, and I was shocked to run strace and see that TcpListener was 1) creating a pipe 2) calling select() even though no timeout was set (something with close_accept). These were creating a thundering herd problem; select returns 'connection is available' to ALL threads, even though only one will then call accept successfully - the rest will get EAGAIN. If the threads were just calling accept() directly, no thundering herd should happen on new linuxes.

So I think this is doing in the right direction, if TcpListener can copy itself using dup(), and accept() with no timeout maps directly to an accept() syscall, without select() messing things up. That should work for 'basic cross platform'. If SO_REUSEPORT was supported, threads (or processes) could just all create their listeners from scratch and not dup anything. I'm trying to use 100% safe code, but I realize stuff like REUSEPORT isn't 100% portable, and you need it to be set before the bind() happens. So us app developers are in a "bind", pun intended...

Also remove some `&mut self` methods as only `&self` is necessary.
@alexcrichton
Copy link
Member Author

@kjpgit

Also, SO_REUSEPORT (a newish option for linux/bsd) would be really, really nice to support as an option http://lwn.net/Articles/542629/

Thanks for the article link! It was a nice read :)

I'd definitely love to support this level of configuration, it's certainly been requested multiple times! I think that something like SO_REUSEPORT is also perfect for an os::unix addition (or even os::linux depending on availability) as an extension of a socket structure.

I think for now we may want to avoid these sorts of options or various configuration for creating new sockets. The provided constructors serve as supplying some reasonable defaults, and various other options can be supported through a separate constructor method.

I would personally prefer to defer the precise method of what a "highly configurable constructor" would look like due to the need to land these basic implementations somewhat quickly in-tree. Some thoughts I have on this topic, however, are:

  1. We could provide a SocketOptions builder-style structure similar to OpenOptions for File which would instruct how to open a socket and what operations to perform. A downside of this, however, is that an error is tough to associate with which option caused the failure.
  2. We could provide a Socket which can then be converted to one of the above types. A Socket would then expose all the various methods for configuration that are supported. A downside of this, however, is that it looks like it'll either not have a huge number of static guarantees or it's got an explosion of types to track.

Either way I think we definitely have some options for pursuing configuration of opening of each network socket type, but for now we shouldn't be closing any doors by taking the conservative option of only providing one default constructor. This is similar to the story for File::open vs File::open_opts for example.

a later date in a more robust fashion with `select`.
* The `accept` function no longer takes `&mut self` and returns `SocketAddr`.
The change in mutability is done to express that multiple `accept` calls can
happen concurrently.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for the concurrent accept. Seems pretty standard, and would work fine for my toy use case at least. EDIT: as long as there's no hidden select() giving you a gun-at-foot thundering herd problem...

@nagisa
Copy link
Member

nagisa commented Feb 4, 2015

It is unclear how socket library initialisation is done on platforms where it is necessary. I.e. on Windows you must call WSAStartup before using sockets and WSACleanup after you finish with them. This RFC does not tell how this is (or is supposed to be) implemented. Are we staying with our current implementation which won’t even free resources after itself?

EDIT: this is API RFC, not an implementation one… Ignore my question

@alexcrichton
Copy link
Member Author

@nagisa that could in theory affect the API actually if we decide to expose explicit setup/teardown functions, although I'm not sure that we would want to do so. I do think it is a bug, however, that we are not calling WSACleanup.

...
}
#[cfg(unix)] impl AsRawFd for TcpListener { ... }
#[cfg(windows)] impl AsRawSocket for TcpListener { ... }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to be able to create TcpStream, TcpListener, and UdpSocket from an AsRawFd or AsRawSocket as well (e.g. file descriptor passing or cross I/O library interface).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this would be quite useful! I think we may want to develop our story around these raw representations a little more before exploring this area, however. The AsRawFoo traits are pretty new and I'd want to make sure that they're sufficient before we start adding conversions in the other direction.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is, however, other interest in this, so we may be able to at least sketch out something #[unstable] for now.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@l0kod
Copy link

l0kod commented Feb 5, 2015

cc rust-lang/rust#14194 (abstract UNIX domain socket)

@nagisa
Copy link
Member

nagisa commented Feb 6, 2015

@l0kod this at best would be unsafe as you could create several objects from the same file descriptor and dropping (= closing) sockets multiple times is invalid.

@l0kod
Copy link

l0kod commented Feb 6, 2015

this at best would be unsafe as you could create several objects from the same file descriptor and dropping (= closing) sockets multiple times is invalid.

Not necessarily if you take ownership. Something like this: fn from_fd(fd: AsRawFd) -> IoResult<TcpStream>.
We could use an into_inner()-like to only keep the inner value without closing it (e.g. file descriptor) and drop the original wrapper.

@alexcrichton
Copy link
Member Author

I have an in-progress implementation PR as well now: rust-lang/rust#22015

The PR specifically fleshes out many details of the IpAddr and SocketAddr types (many thanks to @ktossell in #20785!) and is otherwise largely faithful to the current revision of this RFC.


Some important points of note are:

* The `send` and `recv` function take `&self` instead of `&mut self` to indicate

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

send_to and recv_from?

@frewsxcv
Copy link
Member

Relevant rust issue related to the naming of SocketAddr rust-lang/rust#16595

fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], addr: &A) -> io::Result<usize>;
fn socket_addr(&self) -> io::Result<SocketAddr>;
fn duplicate(&self) -> io::Result<UdpSocket>;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fwiw, there are useful reasons for calling connect() on a UDP socket:

a) In many protocols you are exchanging a stream of UDP packets with a single peer, and connect() can simplify your internal API.

b) In some (non-NAT-friendly) protocols (eg: RTP), you need to find a local IP address to tell the other end about. A reasonably common/portable/clean way to do this is to open a UDP socket, connect() to the peer IP to force the kernel to bind the local end, and then use getsockname() to fetch the IP address the kernel decided to use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree! I believe that this should be done through a future Socket API though instead of having a large number of constructors on UdpSocket.

@anguslees
Copy link

If I may be so bold: By splitting it up into separate TCP client, server and UDP socket objects, I find this proposed API pre-supposes only UDP/TCP types of sockets and that the sockets will be used in particular ways. This places a number of restrictions on the underlying (assuming BSD sockets) API that don't exist at the syscall level. BSD sockets really just has different constructors, and then after that everything is a fd and you call (almost) the same set of syscalls on any socket. That underlying shared syscall structure isn't evident in this proposed API.
This would be fine, except it seems at odds with the other parts of the io revision RFC which offer composable building blocks and go to some length to have a 1:1 with low-level syscalls.

@aturon aturon merged commit cb10c25 into rust-lang:master Feb 18, 2015
@aturon
Copy link
Member

aturon commented Feb 18, 2015

This PR has been belatedly merged (after the actual work had landed); it was left open by mistake. There's not much to debate here, given the very conservative design (and that it is largely just using the existing io traits). The renaming of SocketAddr is likely to land as a follow-up.

@Centril Centril added the A-net Proposals relating to networking. label Nov 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-net Proposals relating to networking.
Projects
None yet
Development

Successfully merging this pull request may close these issues.