Releases: louthy/echo-process
LanguageExt Aff and Eff support in Processes
The previous attempt at a deep integration of language-ext Eff
and Aff
support in echo processes didn't work out (as discussed here).
So, I have done a much simpler implementation:
- That requires no changes to the core actor-system that runs the echo-processes, and so is risk-free for all existing code
- Enables asynchronous processes for the first time
- Facilitates injectable IO operations (via
Eff
andAff
)
To leverage it you should use Process<RT>
rather than Process
as your starting point. There's:
Process<RT>.*
- the core prelude functionsProcess<RT>.spawn*
functions for creating proceses that takeEff
andAff
computationsProcess<RT>.tell*
for telling messages to other processesProcess<RT>.ask*
for request/response to other processesProcess<RT>.reply*
for replies to other processesProcess<RT>.register*
for registering named processes
Because this is a more 'lightweight' approach, the Process<RT>
functions are simply wrappers to the existing Process
functions. That means you don't get to build a DI runtime for Echo itself (even though there is a placeholder trait called HasEcho<RT>
).
I think this is fine as a starting point though, because mostly everything that echo does is managed in-memory and is relatively easy to mock. The main value-add here is that all IO within a process can be mocked, which makes it relatively trivial to build unit-tests for an inbox function.
If you don't care about Eff
or Aff
then this release will have no impact on you. If you want to know more then check out the write-up on the language-ext repo.
Role dispatchers delivery guarantee changes
Role dispatchers which find all online nodes in a role, and deliver messages to some or all of them (depending the dispatcher policy: round-robin, broadcast, least-busy, etc.) had a flaw: in that if there were zero nodes online the tell
would fail with the following error:
No processes in group - usually this means there are offline services.
This was in stark contrast to dispatching to a single named Process, in that it would be queued up for when that process came back online.
The problem with taking this approach for roles is that some nodes may never come back online, and so the persistent store could fill up. Roles are supposed to be dynamic in a way that a single ProcessId
pointing at a known Process is not.
However, there's a middle ground:
- Role dispatchers first use the
Process.ClusterNodes
property to see what nodes have been active in the past four seconds- If there are some, then the
tell
will be sent only to the nodes currently online - This was the entirety of the previous system
- If there are some, then the
- If there aren't any nodes online, then the Role dispatcher will fall back to
Process.ClusterNodes24
- which has a list of the nodes that have been active in the past 24 hours.- It will first try to find nodes active in the past hour, then within two hours, then three, etc. up to 24 hours
- If some nodes have been active recently then the
tell
will be sent to their persisted queue. Waiting for the node(s) to start up
When those nodes restart (if they ever do), they will be able to process the messages as normal.
This allows for periods of downtime, and no lost messages for perhaps single instances of a service that you might be running.
Protection from exceptions thrown by tell, ask, fwd
tell
, ask
, and fwd
(and their variants like tellChild
, etc.) can throw an exception for a number of reasons. Locally sent messages can throw if the process is not available, remotely sent messages can throw if the process has never been created or the message-type is incorrect.
These exceptions (if thrown within a Process inbox or setup function) will cause the Process to restart. For some processes this might be very expensive (caches for example). So, rather than insist on using try
/catch
around tell
, ask
, and fwd
, echo will do that for you and forward the failed message to dead-letters.
Asks obviously require a response. Instead of replacing all ask
functions with new ones that return Fin<A>
, I have now added askSafe
, askChildSafe
, etc. which will catch any exception and return a Fin<A>
. This gives you a chance to deal with the failure in a functional way.
Async + Aff support + major optimisations
This is a big release, which should be treated with some caution - so make sure you have a bit of time to test it before going into a production environment
- Early indications are that performance is 2.5x better (no formal benchmarks yet though)
- Processes now use
ValueTask
internally for first class support ofasync
operations within the setup, inbox, termination, and shutdown functions- Processes are now lock free
- No threads are used if the Process is not processing a message
- The underlying queues were previously used a pausable-blocking-queue implementation which caused two threads to be held for every Process, and so now we only have a thread alive when the message is being processed.
- Support for language-ext
Aff
- New 'prelude' in
Process<RT>
which supportsAff
runtimes - The underlying IO is not yet fully 'injectable', this will come with a future release
- New 'prelude' in
- There are now 32 'ask actors', these are the Processes that receive an
ask
request and auto-resolves the response. The new system shards the ask-actor to use by the request ID. This should lead to performance gains for asks. LocalScheduler
which was used to send scheduled messages within the app-domain has been rewritten to useThreading.Timer
, rather than its own scheduling implementation. This should lead to more accurate scheduling and probably more efficient schedulingRemoteScheduler
which schedules persistent messages has been refactored to store all schedulers for a specific process in a single Redis key - this removes the need to call thekeys %
command in Redis, improvement performance and reliability.- Removed dependency on
Owin.WebSocket
forEcho.Process.Own
- Improved bootstrapping, for a more deterministic setup of the process system
- Only the root Process needs bootstrapping, the rest of the system Processes are now regular echo processes, with no 'special' processing
- Removed the
transactionalIO
system - this wasn't effective enough, and can probably be replaced by a AST with a bespokeAff
runtime (if necessary). - Moved to latest version of language-ext, with use of the new
AtomHashMap
and improvedAtom
stability
Updated to latest language-ext and cleaned up references
Small release to fix up the references/dependencies, some of which had been hanging around since dnx/.NETCore 1 and were causing some headaches. I've also made the dependency versions use more permissive ranges, which should help projects that include echo-process
going forward.