Skip to content

Separating Channels and Actors

Tim Watson edited this page Nov 28, 2018 · 13 revisions

Terminology and clarifications

For the purposes of this document/page:

  • Cloud Haskell refers to the current implementation of distributed-process - (from this repository, in fact), and other libraries authored by Duncan, Edsko, and other brilliant folks from Well Typed, and developed, maintained, and curated by the awesome people at Tweag I/O (Mathieu, Alexander, and Facundo, to mention a few), and Tim Watson

  • Akka refers to the Java/Scala implementation of Akka

  • Akka Streams refers to the Akka Scala API for Reactive Streams

  • Erlang refers to the open source implementation of Erlang/OTP

  • Actor refers to the universal primitive of concurrent computation under the actor model

  • In distributed-process terms, Actor refers to code running in the Process monad, which has been spawned on a local or remote node, using the primitives spawn, spawnLocal, spawnChan, and so on

  • In GHC-Haskell terms, an Actor in distributed-process is a forkIO thread which is managed by the local node infrastructure, in terms of its lifetime, and connection(s) to other Actors in the system (whether distributed or otherwise)

Motivation

Actors communicate with the outside world (and each other) using asynchronous message passing, usually offering some kind of opaque handle for third parties to use when sending messages. Examples of this include the ProcessId type from distributed-process, and the SendPort a type from the same library (which represents the sending end of a typed channel).

Since this form of asynchronous message passing is not a language level primitive in Haskell, we need to consider how code running outside of the Process monad, and thus outside the context of a managed Cloud Haskell node, ought to interact with actors running within the system. One option is to use the runProcess function, which is defined in the API for Control.Distributed.Process.Node, which essentially does the following:

  • create an empty MVar
  • spawn a new Process to run the supplied code (i.e. forkIO and execute the given actor)
  • wait on the actor finishing (i.e. the Process code completing) and write () the MVar
  • have the calling thread wait on the MVar to determine that the actor has finished working

I've elided the details or error handling, and particularly asynchronous exception handling, and inheriting the masking state of the calling thread, and so on...

If we want to send a message to an actor in the system from outside then, we must forkIO a new thread and use thread synchronising techniques (like MVar) to determine that the sending completed. Since sending is asynchronous and should never fail - more in this later - we may instead choose to forkProcess in the calling thread, since we're not waiting on a reply anyway. As long as we do not mind the following actions in the forkIO thread the spawned the new process racing with the code that sends that message, this approach is fine.

Clone this wiki locally