-
Notifications
You must be signed in to change notification settings - Fork 666
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
Calculate nfds
parameter for select
#701
Conversation
src/sys/select.rs
Outdated
/// Finds the largest file descriptor in the set. | ||
/// | ||
/// Returns `None` if the set is empty. | ||
pub fn largest(&self) -> Option<RawFd> { |
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.
I had to make this function public in order to add a test. For some reason putting a #[test]
function in this module didn't run the test.
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.
Hmm, it definitely should. Can you post the code with it written as a private method like you'd expect it to work?
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.
Ah, nevermind, it does work. I probably typo'd #[cfg(test)]
before. But since the other tests for this type are inside test_select.rs
, I'm going to leave it this way unless you object.
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.
I'd rather have largest()
be private. And in general unit tests should be in the same file as the functions themselves, it's only the larger integration tests that should be in /tests
. Let's more the tests into this file at the bottom in a test
module.
test/sys/test_select.rs
Outdated
@@ -31,6 +31,20 @@ fn test_fdset() { | |||
} | |||
} | |||
|
|||
#[test] |
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.
This isn't needed here.
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.
It is, otherwise rustc never calls this method (I just checked by putting a panic!
inside). Maybe you're thinking of some other test file without harness?
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.
Sorry, you're right!
Note that this is technically not zero-cost, but it is pretty close since the array is small. |
src/sys/select.rs
Outdated
for (i, &block) in self.bits.iter().enumerate().rev() { | ||
if block != 0 { | ||
// highest bit is located at `n.trailing_zeros()` | ||
return Some((i * BITS + block.trailing_zeros() as usize) as RawFd); |
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.
Ah, there's a bug in here.
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.
I like makeing FdSet::largest
a public method, but I have misgivings about making it mandatory. Even though select
is not fast compared to kqueue
or the like, and even though FD_SETSIZE
is not super big, changing an O(1) algorithm into an O(n) algorithm is still not good. Better to give the consumer the option of using largest
or simply calculating nfds
himself.
The simplest solution to the test_select
failure is just to pass max(r1, r2) + 1
as `nfds.
src/sys/select.rs
Outdated
@@ -68,11 +87,18 @@ mod ffi { | |||
} | |||
} | |||
|
|||
pub fn select(nfds: c_int, | |||
readfds: Option<&mut FdSet>, | |||
pub fn select(readfds: Option<&mut FdSet>, |
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.
If you're feeling particularly ambitious, this would be a good time to change the definiton of select to something like pub fn select<T: Into<Option<&mut FdSet>>>(readfds: T ...
such that all the Option
types have those kinds of signatures. Then you can use .into()
within the function and the users of the functions don't need to wrap arguments in Some()
. It's really ergonomic!
src/sys/select.rs
Outdated
writefds: Option<&mut FdSet>, | ||
errorfds: Option<&mut FdSet>, | ||
timeout: Option<&mut TimeVal>) -> Result<c_int> { | ||
// calculate nfds parameter, the largest fd we want to select on | ||
let nfds = readfds.iter() | ||
.chain(writefds.iter()) |
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.
Can you indent these to align the "."s vertically? Much more readable then.
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.
Will do, but please consider moving to the proposed RFC style. Most editors make it surprisingly hard to indent code like this.
@asomers It's not O(n), though. The loop runs a maximum of So, what should I do about |
Okay, I just tried to re-enable the test on mips and powerpc, but it looks like it still fails. |
src/sys/select.rs
Outdated
use unistd::{write, pipe}; | ||
|
||
#[test] | ||
fn test_fdset() { |
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.
This should probably be broken into a few smaller tests that make it more clear as to what's being tested. Like break up the adding an Fd
into a test, then test adding & removing, then test clear in a separate test, etc. It makes debugging failures much easier if tests are grouped into a small amount of related tests. This has all the tests in one function.
I still disagree with automatically calculating |
Okay, I'll make that optional |
Interesting making |
src/sys/select.rs
Outdated
@@ -53,6 +53,25 @@ impl FdSet { | |||
*bits = 0 | |||
} | |||
} | |||
|
|||
/// Finds the largest file descriptor in the set. |
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.
I don't think largest
is an appropriate term here, as RawFd
s don't have a size. They have a numbering, however, so highest
might be appropriate here. Also, since this basically exists exclusively for use with select()
, please add a simple example showing how you might use it here.
You need to rebase this and move your entries into the CHANGELOG into the new Unreleased section since this no longer applies to the 0.9 release. I'm not a fan of leaving the |
LGTM. Thanks for all the hard work. bors r+ |
I'm satisfied, but for the CHANGELOG issue. |
bors, wait for the changelog to be fixed. bors r- |
Ah, good catch about the changelog, didn't notice that I broke it during rebase. Should be fixed now. |
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.
One thing that isn't addressed here is how to use the select()
function in the various ways that are now supported, both in documentation and in test code. There are effectively 3 ways to call select()
for its nfds
value: Using highest() + 1
, using a manually specified n+1
value, and using None
. Only the last case is tested and none of these are very well documented. The documentation for highest
doesn't refer to select()
at all and suggest that's where it might be used. Nor do the docs for select()
refer to highest
at all.
CHANGELOG.md
Outdated
@@ -19,6 +19,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). | |||
has changed type from `c_int` to `SockProtocol`. | |||
It accepts a `None` value for default protocol that was specified with zero using `c_int`. | |||
([#647](https://github.com/nix-rust/nix/pull/647)) | |||
- Made `select` easier to use, adding the ability to automatically calculate the `nfds` parameter using the new | |||
`FdSet::largest` ([#701](https://github.com/nix-rust/nix/pull/701)) |
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.
This should be "highest"
@Susurrus Fixed the changelog and updated the docs with links. I can also add another test if you tell me how it should look, but the current logic is so simple that I think a test is not warranted. |
The point of the tests is as much about regressions and ergonomics testing as it is about logic. While the logic may be simple, there are 3 discrete ways to call |
src/sys/select.rs
Outdated
fd_set.insert(r2); | ||
|
||
let mut timeout = TimeVal::seconds(10); | ||
assert_eq!(1, select(fd_set.highest(), |
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.
Isn't this not correct? Shouldn't it be fd_set.highest() + 1
?
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.
Oh, good catch!
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.
It does concern me however that this test didn't fail when ran, however. Shouldn't it have?
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.
Probably the fd that fired the select was the one with the lower number.
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.
There any way to guarantee that's not the case so this test will fail if the +1
isn't there?
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.
Well, that's why I opened the PR is the first place - the existing test wasn't threadsafe and could fail because of this. I guess you could put a mutex around every test that allocates fds...
@jonas-schievink I think we're all ready to commit this, but you'll need to rebase first to fix a trivial merge conflict in the CHANGELOG. |
Doing this behind the scenes makes the API less error-prone and easier to use. It should also fix #679 (comment)
bors r+ |
701: Calculate `nfds` parameter for `select` r=asomers Doing this behind the scenes makes the API less error-prone and easier to use. It should also fix my issue in #679 (comment)
Doing this behind the scenes makes the API less error-prone and easier
to use. It should also fix my issue in #679 (comment)