de-vri-es / serial2-tokio-rs

Other
16 stars 3 forks source link

Deciding if using serial-tokio-rs within an encapsulating sync rust library is advantageous and beneficial, rather than using serial2-rs #13

Closed jerrywrice closed 1 month ago

jerrywrice commented 1 month ago

First, thanks for both the 'serial2-rs' and 'serial2-tokio-rs' crates you (and your colleagues) have published - excellent work!

I'm developing a rust crate library that provides support for a well-established standard industrial automation protocol (SEMI). I'm presently considering if it is beneficial to use serial2-tokio-rs (as opposed to serial2-rs) for my RS-232 support. I'm unclear if serial2-tokio-rs is actually the best choice because my intent is to publish (only) a 'normal' sync public library API. I'm only providing a sync API since rust has not yet experienced serious adoption (trivial at most?) in the chip industrial automation space, so most developers using this library will likely be new rust developers. It will be highly disappointing (to say the least!) if I publish an async API and only then find that most or many industrial developers reject the rust async interface (and thus the library) due to a perceived steep learning curve, or are put off by the debugging challenges which exist for rust async code when using the more popular rust IDEs (this is mentioned on-line in multiple blogs/postings). I've in-fact encountered this myself while recently experimenting with async rust code while using VSCode with rust-analyzer extension enabled. I verified that the debugging problems actually cleared up once I eliminated the 'async' macro from my 'async fn main()' source line. This code in-fact worked as expected if I ran the compiled binary directly from the terminal outside VSCode. Furthermore, the code wasn't even using other async features, but simply had the 'async' macro and keyword present with my main() function's source file.

On the other hand, my interest in considering 'serial2-tokio-rs' rather than 'serial2-rs' is because this standard industrial protocol defines/requires several user configurable sub-second time-out settings per serial port connection. While the minimum value of one of these spec listed time-outs is 100 ms, with a granularity increment of 100 ms. While this doesn't sound overly challenging, some of these spec listed per 'port' time-outs are inter-related, and must be implemented with accurate timing with respect to each other so the protocol functions reliably regarding error recovery. Additionally, the current goal for my library is to support up to one-hundred (100) simultaneously active serial port connections. Therefore I'm considering if serial2-tokio-rs is better suited for this than serial2-rs, based on my understanding of the relative ease of implementing protocol 'read' time-outs using the available async specific select statement, as well as reducing thread usage via async. Regarding my background with rust async, I've not previously developed or used it (I've only recently looked into it). I therefore don't have high confidence in my ability to properly assess whether providing a synchronous high-level API that internally utilizes async library I/O code, is a good idea. I'm also concerned about undertaking a time-consuming 'bench marking' experiment phase, since I might reach erroneous conclusions due to my lack of experience with rust async. I've read some articles and technical information which indicates layering rust sync on top of rust async is problematic, while others dispute this. I'm certainly willing to invest time to carefully learn rust async programming, but I'm not clear if it's a good investment of my development time for this specific project.

My main questions are whether using async serial2-tokio-rs library rather than serial2-rs would be problematic:

  1. Might mixing sync and async impede total multi-port throughput under load since I would be invoking your library's async interface (along with other surrounding async support code) from my higher layer synchronous code. My current understanding is the alternative is to instead only use serial2-rs (sync), with a full multi-threaded design/implementation through-out my library stack.
  2. Would my internal implementation to integrate the async layer be inherently complex, or otherwise problematic? I've read conflicting information (on-line) concerning this potential issue.

Your thoughts or feedback will be very welcome? Thanks in advance for any insight.

+++Clarification: My library is NOT be required to run on an embedded 'no_std' supported computer platform. While embedded would be nice, practically all implementations of this standard automation protocol currently run on Windows or full Linux platforms (systems). In the future I can consider adding a more constrained 'no_std' embedded feature to this current development, or possibly a totally separate new crate (not important now).

de-vri-es commented 1 month ago

Hey!

As an intersting point: serial2 uses a pretty similar mechanism for timeouts as tokio. To be specific, on unix it uses a select call and on Windows it uses "overlapped IO". The big difference is that with serial2, there will be one select call per serial port (and on Windows no completion handler). So it will still block your thread when you read or write.

I personally don't think 100 threads is that much these days, but I think it is enough that asynchronous I/O will provide some real performance benefits. But if those are important enough or not, I don't know. I'm afraid I can't be more precise than that, because the real-world performance is going to depend on a lot of things.

I can tell you that normally, having something async will "bleed" through to your entire program. In order to call async code, you must be in async context. And if you plan on using traits a lot, the result may not be the most comfortable (async functions in traits are still cumbersome to work with, in my opinion).

You can connect sync and async code by using channels to communicate. For example, the channels provided by tokio have both async and sync functions: tokio::sync::mpsc::Sender::send and tokio::sync::mpsc::Sender::blocking_send.

de-vri-es commented 1 month ago

Ah, also, if you want to have a synchronized timeout on all reads from different ports, then tokio makes that a lot easier. So if that is truly important, then you probably don't really have a choice :(

jerrywrice commented 1 month ago

Maarten,

Thanks for the quick response.

I was obviously somewhat confusing in my description of my project details as it relates to the protocol timeouts and multiple active ports, as the relative timeout synchronization I mentioned is only between the various protocol timeouts for each individual port connection (not the overall set of active connections). And I understand your point about having to synchronize timeouts between different ports would only be practical (if possible at all) only with async tokio!

The synchronization between related protocol timeouts I mentioned only relates to each (distinct) active port. There are a small number of distinct serial protocol timeouts for each port: these include a message intra-character timeout, a local and remote end-point single character hand-shake timeout when requesting or responding to a forthcoming message packet transfer, etc… These related timeouts also support a simple type of serial protocol transmission flow control. It’s somewhat awkward to briefly describe these coherently without burdening you with specifics of the actual protocol spec. For example, the serial protocol (termed E4) includes what is referred to as a T1 timeout, a T2 timeout, a T3 timeout, and finally a T4 timeout. These are described in detail the SEMI protocol specification, which I believe I can safely assume you’re not overly interested in! I’ve been working with these for a couple of decades using c/c++ mostly. Honestly, I have to admit it is a bit convoluted working through its details. I assume you have better and more interesting things to devote your ‘bandwidth’ to!

Regarding the option of using serial2-rs rather than serial2-tokio-rs, I’m reasonably confident that I can implement this and scale the number of simultaneously active ports using a crate like Rayon (thread pools). Of course, it will almost certainly require a somewhat sophisticated (per port) state machine (possibly a couple), and likely some per port inter-thread channel handshaking for coordinating various things. I’ll also need to sort out how the ‘per port’ protocol timeouts can be made to function properly and with required accuracy.

Since you’re clearly the expert on Serial2-rs and Serial2-Tokio-rs, do you think that an experienced systems programmer who has used c++ for a couple of decades, and dabbled in synchronous rust for about a year, should be able to reliably and efficiently utilize the serial2-tokio-rs library while invoking it’s async read and write (trait?) methods or associated functions, in a relatively straight-forward fashion from one’s synchronous code layer? As I mentioned in the issue/inquiry I submitted, I do intend for a significant portion of my library code to be implemented as standard synchronous code (certainly the client facing layer).

Jerry W. Rice

From: Maarten de Vries @.> Sent: Monday, August 12, 2024 2:04 PM To: de-vri-es/serial2-tokio-rs @.> Cc: JWR_FABNexus @.>; Author @.> Subject: Re: [de-vri-es/serial2-tokio-rs] Deciding if using serial-tokio-rs in a (primarily) sync rust library is reasonable (Issue #13)

Ah, also, if you want to have a synchronized timeout on all reads from different ports, then tokio makes that a lot easier. So if that is truly important, then you probably don't really have a choice :(

— Reply to this email directly, view it on GitHub https://github.com/de-vri-es/serial2-tokio-rs/issues/13#issuecomment-2284902136 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AEHOLCWUH6I73DT3IU5P4SLZREPLDAVCNFSM6AAAAABMMX7ZLSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEOBUHEYDEMJTGY . You are receiving this because you authored the thread. https://github.com/notifications/beacon/AEHOLCWZKQ7SW2EWXSC2VY3ZREPLDA5CNFSM6AAAAABMMX7ZLSWGG33NNVSW45C7OR4XAZNMJFZXG5LFINXW23LFNZ2KUY3PNVWWK3TUL5UWJTUIGDLPQ.gif Message ID: @. @.> >

jerrywrice commented 1 month ago

Maarten,

Thanks so much for the helpful information (as well as the two crates!). After reviewing your responses, I searched again and found that the Tokio documentation indicates it supports bridging from synchronous code to 'async land'. Here's the link if you're interested . Per your feedback, that is the approach I will pursue and will proceed with 'serial2-tokio-rs to assure better performance with many ports active.

jerrywrice commented 1 month ago

Will use Tokio bridging per link in previous comment.

de-vri-es commented 1 month ago

Alright, good luck!