-
Notifications
You must be signed in to change notification settings - Fork 82
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
ENH: Add POC async implementation, example using storescp #542
Conversation
I've been doing some more reading up, and unfortunately it seems like the options are
|
Much appreciated!
There ought to be a bit of redundancy in this process unfortunately. Whether to go async or not requires a different function "color" which affects its inner workings altogether. At best, there is a way to reduce the amount of redundancy, which is to write non-blocking and non-polling implementations of the PDU readers. While this can be a bit trickier than the existing logic at Let me know if more guidance is needed here. |
Let me make sure I'm understanding where you're going. For the reader implementation, it would be something like:
pub async fn receive(&mut self) -> Result<Pdu> {
loop {
if let Some(pdu) = parse_pdu(&mut self.buffer)? {
return Ok(Some(pdu));
}
if 0 == self.stream.read_buf(&mut self.buffer).await? {
if self.buffer.is_empty() {
return Ok(None);
} else {
return Err("connection reset by peer".into());
}
}
}
} And similar for |
That could work if we use a reader that keeps a buffer of all partial data, so that it can be read multiple times until a complete frame is available. In practice, we may be better off making this function receive a value with a better trait bound than |
I'm not really following, sorry! What kind of trait bound might be better, something like |
Both
I would just try and see if you can tweak the implementation so that the function signature is |
Change is working with async storescp, still need to try how it would work with the sync version
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.
Called out a few specific changes. I have it working again as POC for the storescp command, still need to figure out how the sync client would look. LMK your thoughts/if this is the direction you had in mind
* Finish exposing various methods of client/server as feature-gated async * Finish async PDataWriter (still having issues)
Okay, I'm happier with the implementation now. I have a POC fully working for Orthanc logs:
Can attach the actual capture file if needed I'm hoping you could
After that, I'd like to start writing tests, I just didn't want to write them yet if the interface is going to change. And then I'd like to do some benchmarking of the async vs sync code and also the new sync code (with the framing) vs. the old sync code. LMK your thoughts! Thanks again! |
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.
Thank you for continuing with this PR. There is a lot to go through, and it is not clear from the code what the problem with PData reading/writing could be. I can try testing this against other platforms. Until then, I left some feedback inline for things which should be taken care of.
* Remove unneeded trait bounds on TextCodec * Cleanup some imports * `context` errors instead of unwrapping
Regarding the storescu to storescp. I was not able to reproduce that, I also added real concurrency to Additionally, I changed the options for Where are those files you were testing on? I'd like to see if I can reproduce using that file, or maybe its because you are on windows and there are some minor differences (I'm on linux). I also updated the documentation in |
The file I used was I was using Linux this time, but I can try again on both machines in any case and get back to you. :) |
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 tried the same test again and this time it worked! Aside from the comments inline, all that should be left to do here is fix the tests.
Co-authored-by: Eduardo Pinho <enet4mikeenet@gmail.com>
- so that the tests build and pass without the "async" feature
- add dedicated heading for presentation contexts
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 took the liberty of making some corrections around feature-gating on "async" (this was explained in one of the comments inline). I only have a suggestion for one of the doctests which I would like you to validate, then we're very likely ready to merge.
I would have greatly wished to merge this, but right now the transfer goes wrong whenever the
Our store SCP starts reporting errors of this kind at random:
And often the files won't end up saved to disk (I had to set Also tested with another store SCP (Dicoogle), it also reports errors and returns status code 101h. This could constitute a problem with PDU writing in async mode, so withholding |
I was worried that it was a random/intermittent thing when you said it failed at first but when you ran it again it passed. I will try to reproduce and look into it! |
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 was looking into this right now! Still haven't gotten to the root of the problem, but encountered a few other things in the code worth looking into.
|
||
{ | ||
let mut pdata = scu.send_pdata(pc_selected.id).await; | ||
pdata.write_all(&object_data).await.unwrap(); |
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.
Should probably turn this into a recoverable error.
if let Err(e) = result { | ||
error!("{}", Report::from_error(e)); | ||
if fail_first { | ||
std::process::exit(-2); | ||
} |
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 code is testing the error on task join, but not the application error returned by the task. This worked better on my machine.
if let Err(e) = result { | |
error!("{}", Report::from_error(e)); | |
if fail_first { | |
std::process::exit(-2); | |
} | |
match result { | |
Err(e) => { | |
error!("{}", Report::from_error(e)); | |
if fail_first { | |
std::process::exit(-2); | |
} | |
} | |
Ok(Err(e)) => { | |
error!("{}", Report::from_error(e)); | |
if fail_first { | |
std::process::exit(-2); | |
} | |
} | |
Ok(Ok(_)) => {} | |
} |
Will get to those too. Don't worry about figuring out the sendscu issue, its something within the messed up logic of |
* Make AsyncPDataWriter a proper state machine * Remove use of `<stream>.write_all` which already loops over input data and removes some of our control, switch to manual use of `poll_write` on underlying stream
Okay I believe this is fixed. It only ever came up on sufficiently large files and I believe it had to do with how the tokio I'll be honest feel much better about this implementation too since the original was mostly guided by chatGPT... This implementation I understand much better, and I left a decent amount of inline comments explaining it for future 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.
OK, I did a few more tests and found no more issues. Terrific work!
POC async implementation.
tokio
which controls the importing ofread_pdu
andwrite_pdu
from either[read|write].rs
or[read_nonblocking|write_nonblocking].rs
Client
andServer
which depend on usingread_pdu
/write_pdu
or any read/write to the socket themselves.A couple other notes:
maybe_async
crate.write_chunk_u16
andwrite_chunk_u32
PDataReader
andPDataWriter
since I'm not exactly clear where they should be used or not used (storescp doesn't use those at all, and in my implementation of c-find I didn't end up using either at all either)send
,receive
,establish
, etc. via something liketokio::time::timeout
Also note this is meant to just be one possible implementation, and I totally expect completely rewriting this MR however you want it to look! Thanks in advance!