Skip to content

Releases: louthy/echo-process

LanguageExt Aff and Eff support in Processes

01 Mar 16:26
590473e
Compare
Choose a tag to compare

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 and Aff)

To leverage it you should use Process<RT> rather than Process as your starting point. There's:

  • Process<RT>.* - the core prelude functions
  • Process<RT>.spawn* functions for creating proceses that take Eff and Aff computations
  • Process<RT>.tell* for telling messages to other processes
  • Process<RT>.ask* for request/response to other processes
  • Process<RT>.reply* for replies to other processes
  • Process<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

12 Oct 20:18
Compare
Choose a tag to compare

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 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

12 Oct 17:17
Compare
Choose a tag to compare

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

12 Oct 10:13
Compare
Choose a tag to compare

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 of async 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 supports Aff runtimes
    • The underlying IO is not yet fully 'injectable', this will come with a future release
  • 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 use Threading.Timer, rather than its own scheduling implementation. This should lead to more accurate scheduling and probably more efficient scheduling
  • RemoteScheduler 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 the keys % command in Redis, improvement performance and reliability.
  • Removed dependency on Owin.WebSocket for Echo.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 bespoke Aff runtime (if necessary).
  • Moved to latest version of language-ext, with use of the new AtomHashMap and improved Atom stability

Updated to latest language-ext and cleaned up references

19 Feb 01:09
Compare
Choose a tag to compare

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.