-
Notifications
You must be signed in to change notification settings - Fork 735
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
Round up when converting timeout to ms for epoll_wait() #1615
Conversation
src/sys/unix/selector/epoll.rs
Outdated
// as_millis() truncates, so round up to the nearest millisecond as the documentation | ||
// says can happen. This avoids turning submillisecond timeouts into immediate returns | ||
// unless the caller explicitly requests that by specifying a zero timeout. | ||
.map(|to| { | ||
to.as_millis() | ||
+ if to.subsec_nanos() % 1_000_000 == 0 { | ||
0 | ||
} else { | ||
1 | ||
} | ||
}) | ||
.map(|to| cmp::min(to, MAX_SAFE_TIMEOUT) as libc::c_int) |
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.
We don't need the the remainder, we can just get the subsec_nanos
directly (just a read of a field). Also let's put into a single map
closure. Something like the following should would
// as_millis() truncates, so round up to the nearest millisecond as the documentation | |
// says can happen. This avoids turning submillisecond timeouts into immediate returns | |
// unless the caller explicitly requests that by specifying a zero timeout. | |
.map(|to| { | |
to.as_millis() | |
+ if to.subsec_nanos() % 1_000_000 == 0 { | |
0 | |
} else { | |
1 | |
} | |
}) | |
.map(|to| cmp::min(to, MAX_SAFE_TIMEOUT) as libc::c_int) | |
.map(|to| cmp::min( | |
to.as_millis() | |
// as_millis() truncates, so round up to the nearest millisecond as the documentation | |
// says can happen. This avoids turning submillisecond timeouts into immediate returns | |
// unless the caller explicitly requests that by specifying a zero timeout. | |
+ if to.subsec_nanos() == 0 { 0 } else { 1 } | |
, MAX_SAFE_TIMEOUT) as libc::c_int) |
This will need some manual formatting of the code and the comment.
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.
Closures merged and re-rustfmt'ed.
The remainder is not needed for timeouts between 0 and 1 ms, but without it one extra ms would be added to any exact-integer-ms timeout. The biggest relative error is that a 1ms timeout gets doubled to 2ms. If the timeouts callers specify are a smooth random variable that's no problem, but humans tend to pick nice round numbers.
The docs do say overrun is possible, and I recognize the desire to avoid a divide instruction, however the syscall a few lines later is probably a lot more expensive, especially on a CPU requiring spectre mitigations.
But it's your API and I have no reason to fight about it, so the remainder can go if you still want it gone.
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.
You're right, good catch. Let's do something like the following (will need formatting again):
.map(|to| {
let to_ms = to.as_millis();
// as_millis() truncates, so round up to the nearest millisecond as the
// documentation says can happen. This avoids turning submillisecond
// timeouts into immediate returns unless the caller explicitly requests
// that by specifying a zero timeout.
let to_ms = to_ms + if to_ms == 0 && to.subsec_nanos() != 0 { 1 } else { 0 };
cmp::min(MAX_SAFE_TIMEOUT, to_ms) as libc::c_int
})
Don't need the remainder and we only do it if the timeout is less than 1 millisecond, but not zero.
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.
Any chance you want to apply the same patch to Windows? Related code:
Lines 225 to 231 in c6b5f13
fn duration_millis(dur: Option<Duration>) -> u32 { | |
if let Some(dur) = dur { | |
std::cmp::min(dur.as_millis(), u32::MAX as u128) as u32 | |
} else { | |
u32::MAX | |
} | |
} |
If not it's no problem.
Same patch applied to Windows, on another branch. I don't have any Windows machines, but |
Thanks @yump. If you want to create another pr with the Windows change our CI will test it. You can check it locally using |
Fixes #1614
I'm a total beginner to rust, so there may be a better way to do this. I'm also unfamiliar with async I/O, so it's entirely possible that crossterm is misusing the API. But the
epoll_wait
manpage does say that the timeout will be rounded up.