Skip to content

Commit d25c5f3

Browse files
committed
Merge remote-tracking branch 'aturon/socket-timeouts'
2 parents a59ba94 + 390b78c commit d25c5f3

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

text/0000-socket-timeouts.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
- Feature Name: socket_timeouts
2+
- Start Date: 2015-04-08
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
8+
Add sockopt-style timeouts to `std::net` types.
9+
10+
# Motivation
11+
12+
Currently, operations on various socket types in `std::net` block
13+
indefinitely (i.e., until the connection is closed or data is
14+
transferred). But there are many contexts in which timing out a
15+
blocking call is important.
16+
17+
The [goal of the current IO system][io-reform] is to gradually expose
18+
cross-platform, blocking APIs for IO, especially APIs that directly
19+
correspond to the underlying system APIs. Sockets are widely available
20+
with nearly identical system APIs across the platforms Rust targets,
21+
and this includes support for timeouts via [sockopts][sockopt].
22+
23+
So timeouts are well-motivated and well-suited to `std::net`.
24+
25+
# Detailed design
26+
27+
The proposal is to *directly expose* the timeout functionality
28+
provided by [`setsockopt`][sockopt], in much the same way we currently
29+
expose functionality like `set_nodelay`:
30+
31+
```rust
32+
impl TcpStream {
33+
pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> { ... }
34+
pub fn read_timeout(&self) -> io::Result<Option<Duration>>;
35+
36+
pub fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> { ... }
37+
pub fn write_timeout(&self) -> io::Result<Option<Duration>>;
38+
}
39+
40+
impl UdpSocket {
41+
pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> { ... }
42+
pub fn read_timeout(&self) -> io::Result<Option<Duration>>;
43+
44+
pub fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> { ... }
45+
pub fn write_timeout(&self) -> io::Result<Option<Duration>>;
46+
}
47+
```
48+
49+
The setter methods take an amount of time in the form of a `Duration`,
50+
which is [undergoing stabilization][duration-reform]. They are
51+
implemented via straightforward calls to `setsockopt`. The `Option` is
52+
used to signify no timeout (for both setting and
53+
getting). Consequently, `Some(Duration::new(0, 0))` is a possible
54+
argument; the setter methods will return an IO error of kind
55+
`InvalidInput` in this case. (See Alternatives for other approaches.)
56+
57+
The corresponding socket options are `SO_RCVTIMEO` and `SO_SNDTIMEO`.
58+
59+
# Drawbacks
60+
61+
One potential downside to this design is that the timeouts are set
62+
through direct mutation of the socket state, which can lead to
63+
composition problems. For example, a socket could be passed to another
64+
function which needs to use it with a timeout, but setting the timeout
65+
clobbers any previous values. This lack of composability leads to
66+
defensive programming in the form of "callee save" resets of timeouts,
67+
for example. An alternative design is given below.
68+
69+
The advantage of binding the mutating APIs directly is that we keep a
70+
close correspondence between the `std::net` types and their underlying
71+
system types, and a close correspondence between Rust APIs and system
72+
APIs. It's not clear that this kind of composability is important
73+
enough in practice to justify a departure from the traditional API.
74+
75+
# Alternatives
76+
77+
## Taking `Duration` directly
78+
79+
Using an `Option<Duration>` introduces a certain amount of complexity
80+
-- it raises the issue of `Some(Duration::new(0, 0))`, and it's
81+
slightly more verbose to set a timeout.
82+
83+
An alternative would be to take a `Duration` directly, and interpret a
84+
zero length duration as "no timeout" (which is somewhat traditional in
85+
C APIs). That would make the API somewhat more familiar, but less
86+
Rustic, and it becomes somewhat easier to pass in a zero value by
87+
accident (without thinking about this possibility).
88+
89+
Note that both styles of API require code that does arithmetic on
90+
durations to check for zero in advance.
91+
92+
Aside from fitting Rust idioms better, the main proposal also gives a
93+
somewhat stronger indication of a bug when things go wrong (rather
94+
than simply failing to time out, for example).
95+
96+
## Combining with nonblocking support
97+
98+
Another possibility would be to provide a single method that can
99+
choose between blocking indefinitely, blocking with a timeout, and
100+
nonblocking mode:
101+
102+
```rust
103+
enum BlockingMode {
104+
Nonblocking,
105+
Blocking,
106+
Timeout(Duration)
107+
}
108+
```
109+
110+
This `enum` makes clear that it doesn't make sense to have both a
111+
timeout and put the socket in nonblocking mode. On the other hand, it
112+
would relinquish the one-to-one correspondence between Rust
113+
configuration APIs and underlying socket options.
114+
115+
## Wrapping for compositionality
116+
117+
A different approach would be to *wrap* socket types with a "timeout
118+
modifier", which would be responsible for setting and resetting the
119+
timeouts:
120+
121+
```rust
122+
struct WithTimeout<T> {
123+
timeout: Duration,
124+
inner: T
125+
}
126+
127+
impl<T> WithTimeout<T> {
128+
/// Returns the wrapped object, resetting the timeout
129+
pub fn into_inner(self) -> T { ... }
130+
}
131+
132+
impl TcpStream {
133+
/// Wraps the stream with a timeout
134+
pub fn with_timeout(self, timeout: Duration) -> WithTimeout<TcpStream> { ... }
135+
}
136+
137+
impl<T: Read> Read for WithTimeout<T> { ... }
138+
impl<T: Write> Write for WithTimeout<T> { ... }
139+
```
140+
141+
A [previous RFC][deadlines] spelled this out in more detail.
142+
143+
Unfortunately, such a "wrapping" API has problems of its own. It
144+
creates unfortunate type incompatibilities, since you cannot store a
145+
timeout-wrapped socket where a "normal" socket is expected. It is
146+
difficult to be "polymorphic" over timeouts.
147+
148+
Ultimately, it's not clear that the extra complexities of the type
149+
distinction here are worth the better theoretical composability.
150+
151+
# Unresolved questions
152+
153+
Should we consider a preliminary version of this RFC that introduces
154+
methods like `set_read_timeout_ms`, similar to `wait_timeout_ms` on
155+
`Condvar`? These methods have been introduced elsewhere to provide a
156+
stable way to use timeouts prior to `Duration` being stabilized.
157+
158+
[io-reform]: https://github.com/rust-lang/rfcs/blob/master/text/0517-io-os-reform.md
159+
[sockopt]: http://pubs.opengroup.org/onlinepubs/009695399/functions/setsockopt.html
160+
[duration-reform]: https://github.com/rust-lang/rfcs/pull/1040
161+
[deadlines]: https://github.com/rust-lang/rfcs/pull/577/

0 commit comments

Comments
 (0)