A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ErlangActor.html below:

Module: Concurrent::ErlangActor — Concurrent Ruby

Note:

Edge Features are under active development and may change frequently.

This module provides actor abstraction that has same behaviour as Erlang actor.

Examples

The simplest example is to use the actor as an asynchronous execution. Although, Promises.future { 1 + 1 } is better suited for that purpose.

actor = Concurrent::ErlangActor.spawn(type: :on_thread, name: 'addition') { 1 + 1 }
actor.terminated.value!                  

Let's send some messages and maintain some internal state which is what actors are good for.

actor = Concurrent::ErlangActor.spawn(type: :on_thread, name: 'sum') do
  sum = 0     while true
    message = receive
    break if message == :done
            reply sum += message   
  end
    sum
end

The actor can be either told a message asynchronously, or asked. The ask method will block until actor replies.

actor.tell(1).tell(1)
actor.ask 10                             actor.tell :done
actor.terminated.value!                  
Actor types

There are two types of actors. The type is specified when calling spawn as a first argument, Concurrent::ErlangActor.spawn(type: :on_thread, ... or Concurrent::ErlangActor.spawn(type: :on_pool, ....

The main difference is in how receive method returns.

Let's have a look at how the bodies of actors differ between the types:

ping = Concurrent::ErlangActor.spawn(type: :on_thread) { reply receive }
ping.ask 42                              

It first calls receive, which blocks the thread of the actor. When it returns the received message is passed an an argument to reply, which replies the same value back to the ask method. Then the actor terminates normally, because there is nothing else to do.

However when running on pool a block with code which should be evaluated after the message is received has to be provided.

ping = Concurrent::ErlangActor.spawn(type: :on_pool) { receive { |m| reply m } }
ping.ask 42                              

It starts by calling receive which will remember the given block for later execution when a message is available and stops executing the current scope. Later when a message becomes available the previously provided block is given the message and called. The result of the block is the final value of the normally terminated actor.

The direct blocking style of :on_thread is simpler to write and more straight forward however it has limitations. Each :on_thread actor creates a Thread taking time and resources. There is also a limited number of threads the Ruby process can create so you may hit the limit and fail to create more threads and therefore actors.

Since the :on_pool actor runs on a poll of threads, its creations is faster and cheaper and it does not create new threads. Therefore there is no limit (only RAM) on how many actors can be created.

To simplify, if you need only few actors :on_thread is fine. However if you will be creating hundreds of actors or they will be short-lived :on_pool should be used.

Receiving messages

Simplest message receive.

actor = Concurrent::ErlangActor.spawn(type: :on_thread) { receive }
actor.tell :m
actor.terminated.value!                  

which also works for actor on pool, because if no block is given it will use a default block { |v| v }

actor = Concurrent::ErlangActor.spawn(type: :on_pool) { receive { |v| v } }
actor = Concurrent::ErlangActor.spawn(type: :on_pool) { receive }
actor.tell :m
actor.terminated.value!                  

The received message type can be limited.

Concurrent::ErlangActor.
  spawn(type: :on_thread) { receive(Numeric).succ }.
  tell('junk').   tell(42).
  terminated.value!                      

On pool it requires a block.

Concurrent::ErlangActor.
  spawn(type: :on_pool) { receive(Numeric) { |v| v.succ } }.
  tell('junk').   tell(42).
  terminated.value!                      

By the way, the body written for on pool actor will work for on thread actor as well.

Concurrent::ErlangActor.
  spawn(type: :on_thread) { receive(Numeric) { |v| v.succ } }.
  tell('junk').   tell(42).
  terminated.value!                      

The receive method can be also used to dispatch based on the received message.

actor = Concurrent::ErlangActor.spawn(type: :on_thread) do
  while true
    receive(on(Symbol) { |s| reply s.to_s },
            on(And[Numeric, -> v { v >= 0 }]) { |v| reply v.succ },
                        on(ANY) do |v| 
              reply :bad_message
              terminate [:bad_message, v]
            end)            
  end 
end
actor.ask 1                              actor.ask 2                              actor.ask :value                         actor.ask -1                             actor.ask "junk" rescue $!
actor.terminated.result                  

And a same thing for the actor on pool. Since it cannot loop it will call the body method repeatedly.

module Behaviour
  def body
    receive(on(Symbol) do |s| 
              reply s.to_s 
              body             end,
            on(And[Numeric, -> v { v >= 0 }]) do |v| 
              reply v.succ
              body             end,
                        on(ANY) do |v| 
              reply :bad_message
              terminate [:bad_message, v]
            end)  
  end
end                                      
actor = Concurrent::ErlangActor.spawn(type: :on_pool, environment: Behaviour) { body }
actor.ask 1                              actor.ask 2                              actor.ask :value                         actor.ask -1                             actor.ask "junk" rescue $!
actor.terminated.result                  

Since the behavior is stable in this case we can simplify with the :keep option that will keep the receive rules until another receive is called replacing the kept rules.

actor = Concurrent::ErlangActor.spawn(type: :on_pool) do
  receive(on(Symbol) { |s| reply s.to_s },
          on(And[Numeric, -> v { v >= 0 }]) { |v| reply v.succ },
                    on(ANY) do |v| 
            reply :bad_message
            terminate [:bad_message, v]
          end,
          keep: true)            
end
actor.ask 1                              actor.ask 2                              actor.ask :value                         actor.ask -1                             actor.ask "junk" rescue $!
actor.terminated.result                  
Erlang behaviour

The actor matches Erlang processes in behaviour. Therefore it supports the usual Erlang actor linking, monitoring, exit behaviour, etc.

actor = Concurrent::ErlangActor.spawn(type: :on_thread) do
  spawn(link: true) do     terminate :err   end
  trap   receive  
end
actor.terminated.value!

The methods have same or very similar name to be easily found. The one exception from the original Erlang naming is exit. To avoid clashing with Kernel#exit it's called terminate.

Until there is more information available here, the chapters listed below from a book learn you some Erlang are excellent source of information. The Ruby ErlangActor implementation has same behaviour.

If anything behaves differently than in Erlang, please file an issue.

Chapters or points to be added Class Method Details .default_actor_executorExecutorService Originally defined in module Functions

Returns the default executor service for actors.

.default_executorExecutorService Originally defined in module Functions

Returns the default executor service, may be shared by other abstractions.

.spawn_actor(*args, type:, channel: Promises::Channel.new, environment: Environment, name: nil, executor: default_actor_executor, &body) ⇒ Pid Originally defined in module Functions .terminate_actor(pid, reason) ⇒ true Originally defined in module Functions

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4