ENH: Add POC async implementation, example using storescp#542
ENH: Add POC async implementation, example using storescp#542Enet4 merged 49 commits intoEnet4:masterfrom
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
naterichman
left a comment
There was a problem hiding this comment.
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 DetailsOrthanc 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! |
Enet4
left a comment
There was a problem hiding this comment.
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. :) |
Enet4
left a comment
There was a problem hiding this comment.
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
Enet4
left a comment
There was a problem hiding this comment.
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! |
Enet4
left a comment
There was a problem hiding this comment.
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.
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.
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 |
Enet4
left a comment
There was a problem hiding this comment.
OK, I did a few more tests and found no more issues. Terrific work!

POC async implementation.
tokiowhich controls the importing ofread_pduandwrite_pdufrom either[read|write].rsor[read_nonblocking|write_nonblocking].rsClientandServerwhich depend on usingread_pdu/write_pduor any read/write to the socket themselves.A couple other notes:
maybe_asynccrate.write_chunk_u16andwrite_chunk_u32PDataReaderandPDataWritersince 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::timeoutAlso 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!