Hi,
I am thinking about how to structure an application with user interface that mainly consists of doing io with hardware and network connections. The goal is that the user can see what is not working and then why it is not working. If for example an io connection crashes and then is able to reconnect, the user should be able to see that an error happened and that it is gone again.
What I've done in the past in another language:
put all io components in its own actor-like message handler
each actor has its own bookkeeping of an error state (set, delete Error). The errorstate was basically Map[ErrorCode, Error]
if error state changes, the actor sends a message with new errorstate to supervisor
the supervisor does bookkeeping of all errorstates of all components and sends changed Map[Component, ErrorState] to ui
Addtional challenge: Some errors appear/disappear with high frequency. For that we added sort of cooldown. This was kind of working, but felt quite complex.
How would you structure applications like that in rust? Is there a better/simpler way of doing the error handling than I did in the past?
For fatal errors, Rust uses Result<…, Error>, and the error types have source() that lets you walk the chain to see all the causes.
For transient errors, using channels (crossbeam-channel) would be similar to the actor solution – send errors to the channel, and have a thread that reads the other end and updates the state and UI.
If you have errors happening too frequently to send down a channel, probably throttle them as close to the source as possible.
Rust generally doesn't like global/shared state, so prefer "distributed" handling of errors in functions and objects they're relevant to, and avoid going through some global stateful mechanism if possible.
Hi, thanks for your answer and your articles how you structure your programs.
I think I will try something like this:
Everything io / device related gets its interface
If a ressource needs to automatically reconnect / a retry loop, it becomes its own actor
Transient errors will be transfered to a supervisor with message passing
Would you implement these message handlers / actors rather with threads or with async? It seems, that tokio has more channel implementation than crossbeam, that could be useful. Is it worth dealing with async rust? Time and scheduling could also be important in the future.
Another question I have is, how do test (actor-like) systems, like that? I like the idea of deterministic simulation testing, but I have no experience with that yet. One idea I had, was to run everything during simulation on the single thread tokio runtime to achieve determinism. Do you think, this is possible and practial?
If you need a web user interface, you may need to use async simply because almost all web frameworks are based on async. Other components you need may also be based on async. So this may override other factors in the end.