Previously, we limited the number of streams that could be opened by
the peer but not `accept`ed by the application. However, to guarantee
bounded resource use, applications will typically want to limit the
number of streams they process concurrently. While this could
be *approximately* implemented at the application layer by controlling
calls to accept, that approach has a significant drawback: If streams
are slow to process, the worst-case per-stream latency observed by a
peer who opens the maximum number of streams can be arbitrarily bad,
because streams may be opened above the limit the local application is
willing to process. Reducing the number of unaccepted streams
tolerated can reduce the proportion of streams affected, but reducing
it too low will increase the number of round trips required to open
any given number of streams, increasing average latency significantly.
As a side benefit, this reduces the effort needed for applications to
limit concurrency to a fixed quantity, which is expected to be the
overwhelmingly common case. Should a use case for dynamic concurrency
limits arise, we can expose a setter.
Use vectors instead of boxed slices
The conversion from Vec<u8> into Box<[u8]> is not always for free.
It will call `Vec::into_boxed_slice`, which will call `shrink_to_fit`.
https://github.com/rust-lang/rust/blob/28f03ac4c08fc7ec62428d0b914e1510ce7ee2cb/library/alloc/src/vec.rs#L689
If the `Vec` isn't fully utilized before, this will cause a reallocation
and a copy of all data. This might currently happen with every
outgoing packet.
This change simply keeps things as `Vec<u8>`, which works just fine since
the IO layer can deal with it.
Improves consistency with Endpoint::connect and removes the
possibility of silent blocking should the user pass in a domain
name. The generic API was originally motivated by the convenience of
passing in numeric addresses as strings, but `.parse().unwrap()` is
pretty easy too.