-
Notifications
You must be signed in to change notification settings - Fork 128
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
Overlapped io #55
base: main
Are you sure you want to change the base?
Overlapped io #55
Conversation
The message on the original MergeRequest.
|
Thanks for the PR. I don't use Windows but I will get a colleague to verify this as soon as possible so we can get it merged! |
I think switching to overlapped io on Windows is the right choice, but we need to be careful about (and probably expose) the COMMTIMEOUTs. It's confusing and painful to get the expected system behavior. For reference see my own struggles with tokio-serial: And also what may be the only correct description of COMMTIMEOUTs that exists: https://gitlab.com/susurrus/serialport-rs/-/merge_requests/78#note_343695538 It's likely impossible to use a single timeout value that meets many use cases, so a think that we need:
|
src/windows/com.rs
Outdated
let bytes_to_read = self.bytes_to_read()? as usize; | ||
|
||
if self.timeout.as_millis() == 0 && bytes_to_read < read_size { | ||
read_size = bytes_to_read; | ||
} | ||
if read_size == 0 { | ||
return Ok(0); | ||
} |
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 know if this belongs in the Read
implementation. It:
- Invokes an additional syscall on every
Read
- Prevents expressing 'block until the buffer is full'
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.
Hm there is actually case where ClearCommError
is useful, but in this library code it is probably not the case
It is mentioned here https://github.com/serialport/serialport-rs/blob/main/src/windows/dcb.rs#L54=
bytes_to_read
, if flag is set to True would be necessary to resume read operations.
In any case it can be removed since we initialize this flag to false, but I'm generally not sure what is best approach here as I took PR https://gitlab.com/susurrus/serialport-rs/-/merge_requests/91 as reference
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.
On side note I think maybe we should only do this check if timeout is 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.
Note that in case of zero timeout, overlapped IO actually performs indefinite awaiting
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.
Note that in case of zero timeout, overlapped IO actually performs indefinite awaiting
Agreed, but in some cases this might actually be the desired behavior. I think with Option<Duration>
(see: #50) we could better express 'never timeout' vs 'never block':
None
: Wait untilbuf.len()
bytes have been readSome(0)
: Read any pending data, up-tobuf.len()
. Return immediately if there is no data.Some(x)
: Read untilbuf.len()
bytes have been returned, ortimeout
has expired
So None
and Some(x)
would be deferred to setting COMMTIMEOUTS
, while Some(0)
would rely on bytes_to_read
. I don't know if this is a perfect solution, but I think that serialport-rs
should be able to support the 'never timeout' case.
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.
Sure, I don't mind to improve timeout logic, but this would be a breaking change.
I think we need to improve situation at least in current 4.x.y
version first
Ah thanks, I'm not sure I'm able to do comprehensive tests myself so it would be good to verify it by someone who is more well versed |
e05c174
to
69529a7
Compare
@mlsvrts I added |
I'm not sure if this is expected or not, but when I use
The error message of "failed to fill whole buffer" is confusing, since the purpose of std::thread::spawn(move || {
let mut buf = [0u8; 3];
loop {
println!("Reading data");
read_half.read_exact(&mut buf).unwrap();
println!("Got data: {:02x} {:02x} {:02x}", buf[0], buf[1], buf[2]);
}
}); |
@xobs Actually looking at code base, it doesn't seem to set |
My mistake, thanks. In that case, I suppose this patch is an improvement on the situation, since I get the error with this patchset. However, it doesn't seem to actually solve the problem. If I set the timeout to something large, then the above code gets blocked at However, if I open Tera Term and connect to that serial port after my Rust program closes, the buffer drains into the terminal window and I get all of the data that should have gone to my program. This is in contrast to the current implementation which will just block forever. |
The patch goal is to enable proper split on windows. I don't exactly follow what is the issue you're encountering, API should block only as long as it cannot fill buffer, so if doesn't fill even |
My mistake -- the issue in my code was that on a Raspberry Pi Pico they don't send data until the DTR bit is asserted. Adding I suppose then the only question is whether it should be nonblocking by default. Structs like |
@xobs |
@jessebraham |
Sorry, I've had some vacation time lately (and have also just been pretty busy in general) and sort of lost track of this conversation! I'll try to get to it this week. |
I think it would be possible to simulate blocking, but you'd probably need to use IOCP (I/O completion ports) and a thread which would fill a channel/queue of data from the serial port. You'd wait on this channel to be filled in order to simulate blocking reads. This is because, as far as I understand things, with Windows overlapped I/O you're expected to give it a pool of threads and a handle to watch for I/O-- those threads are woken up and handed data once available, so it's pretty fundamentally different than typical non-blocking I/O on Linux (though I guess the key point @DoumanAsh mentioned is that GetOverlappedResult isn't going signal completion until the whole buffer is filled or a timeout is reached? If doing this "simulated blocking" then you could always set a timeout for the completion port and configure a small buffer size, re-queuing the read in the event of a timeout. |
@jessebraham kindly remind you about PR 😄 |
Okay, I've done some testing with this on a Windows 11 box with an FTDI USB->serial adapter and things seem to be working well. (See #69 for the loopback test I was running). My only remaining note here is to make sure we version control this as a breaking change, as some code that worked before will stop working after this is merged (I've verified this can happen with the timeout behavior, but there may be other subtly broken scenarios). Other than that, it looks good to me! Thanks @DoumanAsh! |
dba08d5
to
28a711d
Compare
I rebased this PR. |
win: Perform zero read size check only when timeout zero win: Add note on how COM port is created Add COM::set_timeouts
28a711d
to
cd79043
Compare
I was thinking about this PR and realized that is it a semver breaking change, and it would actually effect me sadly. Personally I'm fine with it and can maintain a fork internally. But that is one thing to keep in mind. The reason this is a breaking change is because this impl |
Oh it was already noted above. That's what I get for not reading. I am generally in favor of it though. |
Subj
This is a similar request to original repo https://gitlab.com/susurrus/serialport-rs/-/merge_requests/91
I'll need to do some testing myself, but overall it is the same code as in that PR with just some minor improvements