For most setters this was just a case of changing the signature to take
a `VarInt`.
`max_idle_timeout` was a bit more involved in order to maintain a usable
conversion from `Duration`. The internal representation was changed from
`Duration` from `VarInt`, and an `IdleTimeout` newtype was introduced
for the setter signature. `IdleTimeout` contains a `VarInt`-encoded
millisecond value, and has a fallible conversion from `Duration`. The
use of a newtype is preferable to a direct `Duration` -> `VarInt`
encoding since there could be other `VarInt`-encoded values in future
that use a different resolution than milliseconds.
These changes allow callers to avoid the type-level possibility of error
if they have a `VarInt` (or a value that can be converted infallibly
into one). Fallible conversion from `VarInt` is still convenient with
`TryInto`, and callers can handle the conversion errors without the
indirection of `ConfigError`.
Closes#1176.
BREAKING CHANGE: The `max_concurrent_bidi_streams`,
`max_concurrent_uni_streams`, `stream_receive_window`, and
`receive_window` methods on `TransportConfig` now take `VarInt`
arguments and return `&mut Self`. `TransportConfig::max_idle_timeout`
now takes an `IdleTimeout` argument and returns `&mut Self`.
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.
This extends the "cargo bench" benchmark to test transmission of data
on multiple streams in parallel.
Ideally we would want all transmission to take a similar amount of time
(bandwidth shared fairly), and the overall throughput to stay the same
or increase (but never degrade) when multiple streams are in use.
The benchmark can not show the fairness aspect very well, since it only
prints an overall throughput. But it shows that performance with
multiple streams stays similar.
Output:
```
running 4 tests
test large_data_10_streams ... bench: 180,761,260 ns/iter (+/- 12,090,679) = 58 MB/s
test large_data_1_stream ... bench: 18,459,500 ns/iter (+/- 3,804,219) = 56 MB/s
test small_data_100_streams ... bench: 4,600,890 ns/iter (+/- 373,842)
test small_data_1_stream ... bench: 71,019 ns/iter (+/- 34,298)
```
I also increased the size of the LARGE_DATA payload from 128kB to
1MB, since 128kB didn't show quinn peak performance.
Applications sending datagrams are expected to prioritize freshness, at
the cost of reliability if necessary. Blocking datagram sends when the
buffer is full is inconsistent with that, particularly if congestion
arises due to a low-priority bulk data stream. Instead, we should opt to
discard old data in favor of newly sent data. The significant API and
implementation simplification is a nice bonus.
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.