Skip to content

Commit

Permalink
Some documentation laying down thoughts on making stuff async
Browse files Browse the repository at this point in the history
  • Loading branch information
locka99 committed Aug 5, 2021
1 parent 5a3da2e commit 7f3d08a
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 3 deletions.
4 changes: 1 addition & 3 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,7 @@ So starting with `0.4`, the synchronous I/O was replaced with asynchronous I/O.
* Inherently multi-threaded via Tokio's executor.
* Supports timers and other kinds of asynchronous operation.

The penalty for this is that asynchronous programming is _hard_. It's hard even in languages like JavaScript where things like lifetimes and borrowing don't have to be thought about. In Rust, async means reference counting things, frequently having to map the output of one future into another kind and so forth.

In addition Tokio has been the cause of its own problems. The timers in tokio-timer 0.1 were broken for sub-100ms timers and not finegrained but this was resolved in 0.2. Issues with closing streams after splitting a stream into reader / writer portions is also a problem that was only recently solved.
The penalty for this is that asynchronous programming is _hard_ even when the language supplies constructs to support it.

In the new async world a session is a state machine:

Expand Down
63 changes: 63 additions & 0 deletions docs/tokio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Into

OPC UA for Rust uses Tokio for network I/O, timers and for asynchronous scheduling. Tokio
is basically a scheduler and library used in conjunction with Rust primitives `async` and `await`
for asynchronous execution of code..

Within OPC UA for Rust, async is used for.

* Listening for connections
* Making connections
* Handshake
* Read / write portions of messaging
* Timeouts
* Side channel commands to abort connections

Despite that, the implementation is also kind of clunky in ways and future async work should
involve fixing or mitigating that clunkiness.

# Synchronous client API

The client side API is all synchronous externally and async internally. In the future
it would be nice to offer an async and a synchronous API without massively breaking
the existing API.

Breakage is very probable though because the current code uses read-write locks on the session
for synchronous calls which would not be conduicive to async.

1. Remove `Arc<RwLock<Session>>` if possible. e.g. perhaps Session becomes a cloneable facade with internal locks if necessary but make the struct callable from outside without obtaining any lock.
2. Clean up innards of existing sync - async bridge to make use of Tokio, i.e. replace thread::sleep code
with async blocks using async timers.
3. ???
4. Asynchronous / Synchronous interfaces


# Synchronous server API

For the most part it doesn't matter that the server is synchronous because most servers are going
to be set-it-and-forget-it deals. Where it might have an impact is on historical read / update
activities, or setter/getters on variables. Basically there may be situations where the server
calls out to the implementor and it would nice if those call outs were asynchronous in some manner.

# Too many threads

Server and client are spawning too many threads for different aspects of their runtime

* Client spawns TWO tokio executors
* Session has a thread::spawn for an executor
* TcpTransport has its own thread::spawn and executor. This executor can be single or multi threaded
depending on configuration.

In theory Client should be able to execute from a single thread assuming Tokio executor was invoked from
main and async API was used. Even in the synchronous case, then 2 threads should be possible - the
synchronous main thread and the session/transport tokio executor thread.

Server side thread use isn't quite so important but it would be nice if thread use
could be minimized.

# Clunky internal mechanics

There are quit flags, states, timers and too much polling going on. Some of this could be simplified.

Ideally it should be possible for a task to be triggered by a state change such that it can loop but not poll on a timer, but on actual change of date it is interested in.

0 comments on commit 7f3d08a

Please sign in to comment.