# Modelo de Atores

Se você leu a primeira parte do guia, deve se lembrar das [propriedades de um processo](https://concorrencia101.leandronsp.com/parte-i-concorrencia-no-sistema-operacional/propriedades-de-um-processo):&#x20;

* processos têm **estado privado** e não compartilham memória
* processos se comunicam uns com os outros por **envio de mensagens** (IPC)
* processos têm um **identificador único** no sistema (PID)

Por conta destas propriedades, um processo não está sujeito aos memos problemas de **race conditions** igual às *threads -* embora seja possível se processos utilizarem mecanismos de compartilhamento de memória de forma explícit&#x61;*.*&#x20;

Com isto, podemos pensar numa possível abstração para trabalharmos com threads sem precisarmos recorrer ao uso de locks.

É como se tivéssemos "threads especiais" que iriam possuir as mesmas propriedades de um processo: estado privado, identificador único e comunicação por envio de mensagens.

Você acertou, estamos falando do **modelo de atores**.

> Muito bacana isso, Leandro. O kernel fornece tal estrutura?

*Não*. Temos de criar nossa própria abstração, a nível de **user space** (runtime).&#x20;

Existem diversas implementações de modelo de atores em diferentes linguagens de programação. Pra mencionar alguns casos, em **Java**, temos implementação de modelo de atores com a biblioteca *Akka***.** Em **Erlang**, a modelagem já faz parte das estruturas internas do runtime. E em **Ruby** (a partir da versão 3+), temos os *Ractors*.&#x20;

## Funcionamento do modelo de atores

Para entendermos o funcionamento do modelo de atores e como este se relaciona com Ruby, vamos pensar em como poderíamos implementar nosso próprio modelo. Não é difícil, acredite.

<figure><img src="/files/aB6LKvG478U0ePz9mqhr" alt=""><figcaption></figcaption></figure>

No exemplo acima, repare que, diferente da abstração de **Thread** que compartilha memória, um **Ator** não compartilha memória com outros atores. Isto por si só elimina problemas inerentes a condições de corrida.

> Acho que entendi, Leandro. Então quer dizer que todo ator usa uma kernel thread por trás?

*Depende*. Algumas implementações podem fazer com que cada ator seja mapeado diretamente para uma kernel thread, enquanto que em outras, um ator é uma abstração bastante leve dentro do runtime, que faz a *multiplexação* de atores para kernel threads conforme outros critérios, diminuindo assim o overhead com a criação de kernel threads.

Neste tópico, para fins didáticos, vamos **mapear cada ator diretamente para uma kernel thread**.

### 1. O ator deve ter estado privado

Para representar o ator, vamos mapear para uma *thread*:

```ruby
actor = Thread.new do
  # Lógica do ator aqui
end

actor.value # nil
```

O método `.value` faz a mesma coisa que o `.join` , mas traz o valor da **última expressão dentro do bloco**.

Por enquanto o ator não tem nenhuma lógica implementada. Vamos iniciar o estado **dentro da thread**:

```ruby
actor = Thread.new do
  state = 41
  # Faz coisas...
  state += 1
  state
end

actor.value # 42
```

A variável `state` foi criada apenas dentro do escopo da thread. Se tentarmos acessar a variável no escopo fora da thread, o programa lança uma exceção:

```ruby
state
# undefined local variable or method `state' for main:Object (NameError)
```

Para além disso, podemos passar argumentos para a thread no momento da criação, e receber os argumentos **dentro do escopo do bloco** da thread:

```ruby
actor = Thread.new(41) do |state|
  state += 1
  state
end

actor.value # 42
```

Foi passado para a thread o valor  absoluto `41` , mas podemos também passar variáveis:

```ruby
balance = 41

actor = Thread.new(balance) do |state|
  state += 1
  state
end

actor.value # 42 - a thread modificou seu estado interno
balance     # 41 - Wow! a thread não modificou o valor original de `balance`
```

Repare atentamente que a variável `balance` criada fora do escopo da thread não foi modificada, apesar da thread ter modificado seu estado interno. É isto que precisamos. Contudo, o quê aconteceu de fato ali?&#x20;

O Ruby, para alguns tipos de dados primitivos incluindo *números inteiros*, faz a cópia quando estes são enviados como argumentos para métodos, ou seja, **a passagem é feita por valor**.

Mas para outros tipos, como os *arrays e hashes*, não é feita a cópia mas sim a **passagem por referência**, pelo que a thread iria modificar o valor original, estando sujeita à race condition.&#x20;

Vamos a um exemplo de um ator que adiciona um elemento em um array:

```ruby
inbox = []

actor = Thread.new(inbox) do |queue|
  queue.push(42)
end

inbox # [42] - Ouch! O array original foi modificado pela thread! Not good...
```

Não é isto que queremos. O nosso "ator" está sendo capaz de modificar variáveis que foram criadas fora de seu escopo. E se forçarmos uma cópia explícita do array para a thread? Há solução pra isso em Ruby, com o método `.dup`  :&#x20;

```ruby
inbox = []

# No momento do dup, é literalmente feita uma cópia do array inteiro e passada 
# como argumento para a thread
actor = Thread.new(inbox.dup) do |queue|
  queue.push(42)
end

inbox # []
```

Prontinho, já conseguimos cumprir com o primeiro requisito para modelo de atores: **estado privado.** Vamos ver o próximo requisito.

### 2. O ator deve ter identificação única

Para que atores conversem uns com os outros, é preciso que cada um tenha uma identificação única, assim como os processos no SO têm PID.

Em Ruby, cada objeto tem um ID, e com **Thread** não é diferente:

```ruby
t1 = Thread.new {}
t2 = Thread.new {}
t3 = Thread.new {}

t1.object_id # 1594340
t2.object_id # 1594341
t3.object_id # 1594342
```

*Yay*! Já temos o segundo requisito cumprido. Vamos ao terceiro requisito e não menos importante: **envio de mensagens**.

### 3. O ator deve se comunicar por envio de mensagens

Levando em conta que a nossa implementação de ator é uma abstração em cima da **Thread**, como enviar mensagens para o ator? A classe `Thread` não fornece uma forma de enviar mensagens, então temos que criar o nosso próprio mecanismo.

```ruby
actor = Thread.new do
  # Implementação
end

# undefined method `send_message' for #<Thread (irb):1 run> (NoMethodError)
actor.send_message("Hello")
```

Pois é, simplesmente não funciona...não há nada na implementação de **Thread** que permita o envio de mensagens, pois a thread é apenas uma abstração em cima de **kernel threads** que são automaticamente escalonadas pelo sistema operacional.

Para implementar o envio de mensagens entre atores, vamos antes entender duas características principais sobre o envio: **síncrono e assíncrono**.

### 3.1. Envio de mensagens síncrono

Num modelo síncrono, um processo **Sender** precisa enviar uma mensagem a outro processo **Receiver**:

<div><figure><img src="/files/HZhbusqisKgP4lJEqwns" alt="" width="375"><figcaption></figcaption></figure> <figure><img src="/files/m2GdquO3dEHD0XuJuE7r" alt="" width="375"><figcaption></figcaption></figure></div>

Enquanto o receptor não confirma que recebeu e processou a mensagem, o processo que enviou a mensagem não consegue fazer outra tarefa, **portanto fica bloqueado**.

Mas o quê acontece caso o receptor este indisponível ou não tenha conseguido processar a mensagem? O processo **Sender** fica sem saber se a mensagem foi recebida, e a mensagem é descartada:

<figure><img src="/files/9OV9uE73Wk69r7W0poah" alt="" width="375"><figcaption></figcaption></figure>

Ou seja, além de ter deixado o **Sender** bloqueado, a *mensagem ficou perdida pra sempre*. Para implementar modelo de atores, precisamos fazer com que o ator seja capaz de **receber mensagens de forma assíncrona**.

### 3.2. Envio de mensagens assíncrono

No modelo assíncrono, a ideia é fazer com que a mensagem caia numa espécie de "caixa de correio" - similar ao que temos na vida real, ou então à *caixa de entrada* de email -, pelo que o ator fique verificando de *tempos em tempos* se chegou mensagem nova.

O ator é obrigado a **responder** tais mensagens? Não. Ou seja, neste modelo, o processo que envia a mensagem não necessariamente precisa ter uma resposta **síncrona** de que foi enviada, mas precisa ter a garantia de que a mensagem se encontra na caixa de entrrada.

<figure><img src="/files/URiHysbzNX6zLv80OhMc" alt="" width="563"><figcaption></figcaption></figure>

Neste modelo, o processo **Sender** não fica bloqueado, ou seja o **envio de mensagem foi assíncrono**. Como podemos implementar esta "caixa de entrada"?

### 3.3. Fila de mensagens

Vamos imaginar que neste caso as mensagens precisam ser processadas na ordem em que chegaram, correto? É um modelo onde *o primeiro que entra é o primeiro a sair*, que em inglês significa *first-in, first-out*, ou **FIFO**.

*FIFO* mesmo, você acertou, vamos implementar esta caixa de entrada com filas!&#x20;

> Que bem sabemos, filas podem ser implementadas com *arrays*!

<figure><img src="/files/ABkZTv6qdMFCtM4LIAB8" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="/files/MokqymjNz8GQWVCQg8Wr" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="/files/vZisvMjKguu8WeQWN5lU" alt="" width="563"><figcaption></figcaption></figure>

Em Ruby, poderíamos representar nossa caixa de entrada (fila) do ator como simplesmente **inbox,** e dentro do ator consumir mensagens desta fila com o método **pop:**

```ruby
inbox = []

actor = Thread.new(inbox) do |inbox|
  inbox.pop
end

actor.value # nil
```

E para *enviar mensagens* para a fila a partir do processo principal, temos o método **push:**

```ruby
inbox.push(42)
```

Entretanto, a esta altura o ator já terminou sua execução, portanto precisamos modificar o ator para que **fique em loop** verificando se há mensagens na inbox:

```ruby
inbox = []

actor = Thread.new(inbox) do |inbox|
  loop do 
    puts "Message: #{inbox.pop}"
  end
end

actor.join
```

O que temos na saída é isto:

```
Message:
Message:
Message:
Message:
Message:
Message:
Message:
Message:
Message:
....................
```

*Infinitamente*. Not good. Queremos que o ator fique meio que *suspenso* quando não houver mensagens na fila.&#x20;

Para resolver este problema, precisamos criar uma **fila bloqueante**, que bloqueia o ator para não ficar gastando CPU desnecessariamente num loop infinito.

### 3.4. Fila bloqueante (e thread-safe) de mensagens

A implementação de uma fila bloqueante pode ser feita com **exclusão mútua**, como já vimos em tópicos anteriores, onde:

* a thread verifica se há mensagens na fila:
  * se houver, faz o **pop** e volta ao início do loop
  * se não houver, utiliza **mutex** para enviar um sinal à thread que precisa ficar suspensa
* quando o processo **Sender** colocar mensagem na fila, é enviado um sinal à thread que está suspensa:
  * a thread retoma de onde parou, consome mensagem da fila e repete o voltando ao início do loop

<div><figure><img src="/files/dk4VrwISdlnyf7lkGZkE" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="/files/cuqWIrNoR5neWsj4TrRq" alt="" width="563"><figcaption></figcaption></figure></div>

Precisamos recorrer a mutex e **condvar** para que a fila seja **thread-safe,** ou seja, a fila precisa ser compartilhada de forma segura entre threads, sem criar condições de corrida.

> Este conceito já foi explicado no módulo de [Concorrência em C](https://concorrencia101.leandronsp.com/parte-ii-concorrencia-em-diferentes-linguagens/concorrencia-em-c/thread-pool-em-c#mutex-e-condvar-em-c).

Além de Mutex, em Ruby também temos uma abstração para o uso de [Conditions](https://ruby-doc.org/core-3.0.2/ConditionVariable.html):

```ruby
inbox = []                                  # Representação da fila
mutex = Mutex.new                           # Mutex
condvar = ConditionVariable.new             # Condition

actor = Thread.new(inbox) do |inbox|
  # Thread fica em loop infinito
  loop do 
    mutex.synchronize do
      # Aqui, é enviado um "sinal" para a thread ficar em estado de WAIT
      #  no caso da fila estar vazia
      condvar.wait(mutex) if inbox.empty?

      # Quando a thread recebe o sinal de WAKE, continua o processamento
      puts "Received message: #{inbox.pop}"
    end
  end
end

mutex.synchronize do
  # Adiciona mensagem na fila e envia sinal de WAKE à thread que detém o mutex
  inbox.push(42)
  condvar.signal
end

# Espera 5 segundos antes de finalizar o programa, a título de vermos a mensagem
#  ser processada a tempo no terminal
sleep 5
```

```
Received message: 42
```

*Yay*! Que dia maravilhoso, não é mesmo? Inclusive, podemos implementar uma abstração de *Inbox* utilizando a técnica milenar de **um array, um mutex e uma variável condicional**:

```ruby
class ThreadSae
  def initialize
    @queue = []
    @mutex = Mutex.new
    @condvar = ConditionVariable.new
  end

  def send(message)
    @mutex.synchronize do
      @queue.push(message)
      @condvar.signal
    end
  end

  def receive
    @mutex.synchronize do
      @condvar.wait(@mutex) if @queue.empty?
      @queue.pop
    end
  end
end

inbox = BlockingQueue.new
inbox.send(42)

inbox.receive # 42
```

> Mas Leandro, sério que temos que implementar nossa própria fila bloqueante em Ruby?

Calma jovem, estamos com sorte hoje! Ruby já fornece uma classe pra isso, a `Thread::Queue` , que é totalmente *thread-safe*:

```ruby
queue = Thread::Queue.new

actor = Thread.new(queue) do |inbox|
  loop do 
    puts "Received message: #{inbox.pop}"
  end
end

queue.push(42)

sleep 3
```

Okay, agora que vimos os fundamentos de modelo de atores com exemplos em Ruby, já conseguimos cumprir com as principais características de um ator:

* estado privado
* identificação única
* envio de mensagens através de fila thread-safe

A seguir, vamos trazer uma série de exemplos impementando a abstração de **Ator** com todas as propriedades que já exploramos.

***

## Um ator que recebe mensagens

Vamos iniciar a implementação com uma classe Ruby bastante simples:

```ruby
class Actor
end
```

A seguir, no método initialize definimos a fila de mensagens e a **Thread** que irá ficar em background processando as mensagens:

```ruby
class Actor 
  def initialize
    @inbox = Thread::Queue.new

    Thread.new do 
      loop do
        # Implementação
      end
    end
  end
end
```

A implementação do ator é muito simples:

* lê mensagem da inbox
* processa mensagem:
  * se "exit", sai do loop e a thread é finalizada, encerrando o ator
  * em qualquer outro caso, imprime a mensagem recebida

```ruby
......
    Thread.new do 
      loop do
        message = @inbox.pop

        case message
        when :exit
          break
        else
          puts "Received message: #{message}"
        end
      end
    end
.....
```

Agora, no modelo de ator definimos um método `send` para o envio de mensagens para a fila e outro chamado `exit` que encerra o ator:

```ruby
class Actor 
  def initialize
    @inbox = Thread::Queue.new

    Thread.new do 
      loop do
        message = @inbox.pop

        case message
        when :exit
          break
        else
          puts "Received message: #{message}"
        end
      end
    end
  end

  def send(message)
    @inbox.push(message)
  end

  def exit
    @inbox.push(:exit)
  end
end

##############################

actor = Actor.new
actor.send(42)     # Envia mensagem para o ator (async)

sleep 3
```

Saída esperada:

```
Received message: 42
```

***

## Um ator que recebe mas também envia mensagens

Para além da capacidade de receber mensagens em uma **fila inbox**, um ator também deve ser capaz de enviar mensagens para o mundo externo, **sempre de forma assíncrona**, utilizando uma fila de mensagens.&#x20;

Mas ao invés de usar a inbox, podemos definir *outra fila*, que irá representar a **caixa de saída**, ou *outbox.*

<figure><img src="/files/PTcDjqN26ZwFzww0yofS" alt="" width="563"><figcaption></figcaption></figure>

> O envio de mensagens deve ser **sempre assíncrono,** ou seja, a mensagem deve ser colocada em alguma fila

Para ilustrar isso melhor,  vamos modificar o exemplo para representar uma conta bancária, que realiza depósitos e saques, mantendo um **estado privado** que é o saldo final da conta. Olha só que belezura fica isso em Ruby:

```ruby
class Account
  def initialize
    @inbox = Thread::Queue.new    # Caixa de entrada
    @outbox = Thread::Queue.new   # Caixa de saída
    @balance = 0                  # Estado inicial do ator

    Thread.new do 
      loop do
        message = @inbox.pop      # Consome mensagem

        # OMG! Pattern matching é mesmo lindo, não?
        case message
        in deposit: amount  then @balance += amount
        in withdraw: amount then @balance -= amount
        in :balance         then @outbox.push(@balance) # Coloca saldo na caixa de saída
        end
      end
    end
  end

  def deposit(amount)
    @inbox.push(deposit: amount)
  end

  def withdraw(amount)
    @inbox.push(withdraw: amount)
  end

  def balance
    @inbox.push(:balance)
    @outbox.pop # Lê a mensagem que o ator deixou na caixa de saída
  end
end

account = Account.new
account.deposit(100)
account.withdraw(50)

puts "Balance is: #{account.balance} (expected: 50)"
```

Este código dispensa maiores comentários, não acha?

***

## Um ator ultra genérico em Ruby

O nosso ator atual está amarrado com a lógica de *conta bancária*. E se criarmos uma abstração de **modelo de ator** que poderia funcionar com qualquer domínio de negócio?

Utilizando o conceito maravilhoso de **blocks**, podemos definir um ator super genérico que repassa os argumentos (estado inicial) e também o bloco de execução dinâmico:

```ruby
class Actor
  def initialize(*args, &block)
    # Define as filas de mensagens (entrada e saída)
    @inbox  = Queue.new
    @outbox = Queue.new

    # Definição da thread em background, que vai avaliar o bloco dinâmico passado para o ator
    Thread.new do
      # Execução do block e resultado de retorno, enviando os argumentos
      #  que foram passados para o ator. Estes argumentos definem o "estado"
      #  do ator e vão ser repassados para o bloco
      result = block.call(self, *args)

      # Utiliza o retorno do bloco para chamar um método que "transfere"
      #  o controle assíncrono, ou seja, coloca o resultado na caixa de saída
      self.yield(result)
    end

    # Após a instanciação do objeto do ator, retorna a própria instância para
    #  ser usada em outro contexto
    self
  end
end
```

A seguir, vamos definir 4 métodos no ator:

* `send` , que coloca mensagem na inbox
* `receive` , que lê mensagem da inbox
* `yield` , que colcoa mensagem na outbox
* `take` , que lê mensagem da outbox

Implementação final do ator genérico:

```ruby
class Actor
  def initialize(*args, &block)
    @inbox  = Queue.new
    @outbox = Queue.new

    Thread.new do
      result = block.call(self, *args)

      self.yield(result)
    end

    self
  end

  def receive
    @inbox.pop
  end

  def send(element)
    @inbox.push(element)
    self
  end

  def yield(element)
    @outbox.push(element)
  end

  def take
    @outbox.pop
  end
end
```

E agora, podemos utilizar este ator para diversos cenários, como no caso de uma conta bancária:

```ruby
account = Actor.new(0) do |instance, balance|
  loop do
    message = instance.receive

    case message
    in deposit: value  then balance += value.to_i
    in withdraw: value then balance -= value.to_i
    in :balance        then instance.yield(balance)
    end
  end
end

100.times do
  account.send(deposit: 1)
end

account.send(:balance)
balance = account.take

puts "Balance is: #{balance} (expected: 100)"
```

Ou ainda para outro contexto, como no caso de pontuação em um jogo:

```ruby
game = Actor.new(0) do |instance, score|
  loop do
    message = instance.receive

    case message
    in :score then instance.yield(score)
    in :increment then score += 1
    end
  end
end

42.times do
  game.send(:increment)
end

score = game.send(:score).take
puts "Score is: #{score} (expected: 42)"
```

> Que incrível, Leandro! Esse Ruby é mesmo lindo, não?

Sim, Ruby é demais. Mas calma que não paramos por aí. A partir da versão 3.0, Ruby trouxe uma abstração por cima da **Thread** que resolve o problema de race condition sem precisar de mutex, justamente implementando um **modelo de atores**, através da [classe Ractor](https://ruby-doc.org/core-3.0.2/Ractor.html), que faz *exatamente tudo o que implementamos até aqui.*

***

## Modelo de atores com Ractors

É isso mesmo, Ruby 3+ traz o conceito de atores prontinho pra gente, bastando definir como será o modelo conforme nosso requisito:

```ruby
account = Ractor.new(0) do |balance|
  loop do
    message = Ractor.receive # Lê mensagem da inbox

    case message
    in :balance        then Ractor.yield(balance) # Coloca mensagem na outbox
    in deposit: value  then balance += value.to_i
    in withdraw: value then balance -= value.to_i
    end
  end
end

account.send({ deposit: 100 }) # Envia mensagem para a inbox do ator
account.send({ withdraw: 50 }) # Envia mensagem para a inbox do ator

account.send(:balance) # Envia mensagem para a inbox do ator
balance = account.take # Lê mensagem da outbox do ator

puts "Balance is: #{balance} (expected: 50)"
```

Quero reforçar aqui que o Ractor em Ruby ainda está em fase experimental, portanto **não deve ser usado em produção**.

Entretanto, acredito que muito em breve teremos isto pronto pra produção. Atualmente temos uma excelente oportunidade para contribuir, deixando a implementação de Ractors em Ruby mais robusta ao longo do tempo.

### Ractors não acessam valores globais ou fora do contexto

Para que seja thread-safe, uma característica muito importante do Ractor é que não acessa valores globais:

```ruby
LIST = []

Ractor.new { LIST.push(42) }
```

O que lança um erro:

```
#<Thread:0x0000000102ec5740 run> terminated with exception (report_on_exception is true):
(irb):35:in `block in <top (required)>': can not access non-shareable objects in constant Object::LIST by non-main Ractor. (Ractor::IsolationError)
```

Uma forma de resolver é recebendo a lista no bloco, pelo que o Ruby irá **copiar** o valor de `LIST` para dentro do Ractor:

```ruby
Ractor.new(LIST) { |list| list.push(42) }

# LIST segue como array vazio, pois foi copiado inteiramente para dentro do Ractor
LIST
```

O mesmo vale para qualquer valor passado. É sempre **copiado**, e não movido:

```ruby
original_list = [1, 2, 3]

ractor = Ractor.new(original_list) do |copied_list|
  copied_list.push(42) # Modifica a cópia recebida
  copied_list
end

puts "Original LIST: #{original_list.inspect}" # Não foi alterada
puts "Modified list inside Ractor: #{ractor.take.inspect}" # A cópia foi alterada
```

### Movendo valores para dentro de um Ractor (alô, Rust!)

Já vimos que não é possível modificar um valor fora do contexto do Ractor pois é feita uma cópia. E se tentarmos enviar a lista como mensagem para o Ractor? Será que conseguimos?

```ruby
original_list = [1, 2, 3]

ractor = Ractor.new do
    list = Ractor.receive
    list.push(42)
end

ractor.send(original_list)

original_list # [1, 2, 3]   Original não modificado (ainda bem!)
ractor.take # [1, 2, 3, 42] Cópia modificada
```

O Ractor continua garantindo a integridade dos dados, completamente **thread-safe**! Entretanto, se quisermos **mover** a referência da lista para dentro do Ractor, podemos fazer passando o argumento `move: true` :&#x20;

```ruby
original_list = [1, 2, 3]

ractor = Ractor.new do
    list = Ractor.receive
    list.push(42)
end

ractor.send(original_list, move: true)
```

Vamos confirmar como está o valor da lista dentro do Ractor:

```ruby
ractor.take # [1, 2, 3, 42]
```

Continua na mesma, então vamos ver o valor original, se foi modificado ou não:

```ruby
original_list # #<Ractor::MovedObject:0x0000000107265810>
```

*Superb!* O valor foi **movido**, então isto significa que após o *move*, não podemos mais utilizar o valor original fora do contexto do Ractor, apenas dentro dele.

Desta forma, não foi feita uma cópia, mas sim um *move*.

> Salve Rustaceans, vocês estão se sentindo em casa agora, né?

### Filas podem ser construídas a partir de atores

Eu sei que você está agora só o meme da Nazaré, mas já vimos que para criar um ator, precisamos apenas de filas. Mas também podemos criar uma fila **thread-safe** (ou melhor, *ractor-safe*) baseada em Ractors.

Tudo o que precisamos é que o ator:

* fique bloqueado a espera de mensagens (`Ractor.receive`)
* repasse as mensagens para a caixa de saída (`Ractor.yield`)

```ruby
queue = Ractor.new do 
  loop do 
    Ractor.yield(Ractor.receive)
  end
end

queue.send(42)
queue.take # 42
```

*No way!* Acabamos de implementar uma fila utilizando Ractors! Agora me diga, caro leitor, quem vem primeiro? O **ovo ou a galinha**? Fica como lição de casa :smile:

<figure><img src="/files/LEpVmAjNjKyTlPcva1Jo" alt="" width="375"><figcaption><p>modelo de atores</p></figcaption></figure>

***

## Tolerância a falhas em Ruby com Ractors

O modelo de atores traz um desafio crucial  em sistemas baseados neste modelo. Processos assíncronos **vão falhar**. Não é uma questão de *se*, mas *quando*.

Quando entramos no mundo assíncrono com modelo de atores, temos que ter algum grau de **tolerência a falhas,** ou seja, lidar com erros e estabelecer planos de ação bem definidos para que os atores continuem operantes mesmo com falhas pontuais.

Em Ruby, infelizmente, neste momento não temos nada que implemente uma forma de **supervisionar Ractors que falham.**

> Mas a gente é maluco e implementa a nossa própria supervisão, né?

Com certeza. Antes de encerrar este tópico em Ruby, vamos implementar um modelo muito simples de supervisão.

### Um ator que representa uma conta bancária

Ainda no exemplo de uma conta onde é possível realizar depósitos e saques, a seguir definimos como é este modelo utilizando Ractors e o mais puro suco da **orientação a objetos**:

```ruby
class Account 
  def initialize(balance)
    @actor = Ractor.new(balance) do |balance|
      loop do
        message = Ractor.receive

        case message
        in deposit: amount  then balance += amount
        in withdraw: amount then balance -= amount
        in :balance         then Ractor.yield({ balance: balance })
        end
      end
    end
  end

  def deposit(amount)
    @actor.send({ deposit: amount })
  end

  def withdraw(amount)
    @actor.send({ withdraw: amount })
  end

  def balance
    @actor.send(:balance)
    @actor.take[:balance]
  end
end
```

* uma conta começa com um saldo inicial, enviado no construtor
* no construtor, temos a definição do **ator** que, através dos métodos públicos do objeto `Account` , recebe as mensagens para alterar o estado da conta bancária

<figure><img src="/files/HxCrCJorMpxMMNGbKLzj" alt="" width="563"><figcaption></figcaption></figure>

Criando um cenário "feliz", vamos manipular a conta bancária:

```ruby
account = Account.new(0)
account.deposit(100)
account.withdraw(50)

puts "Balance is: #{account.balance} (expected: 50)"
```

Tudo perfeito até aqui, mostrando o saldo corretamente na saída do programa. Agora, vamos adicionar um pouco de entropia, simulando um *crash* no ato&#x72;*:*

```ruby
# Adicionando mais matches na mensagem da inbox:

case message
in deposit: amount  then balance += amount
in withdraw: amount then balance -= amount
in :balance         then Ractor.yield({ balance: balance })
in :crash           then raise "Crash!" # <------------------------
end

.........
  def simulate_crash!
    @actor.send(:crash)
  end
.........
```

Simulamos o crash, então a seguir um depósito e a busca do saldo:

```ruby
account.simulate_crash!
account.deposit(200)

puts "Balance is: #{account.balance} (expected: 250)"
```

```
Balance is: 50 (expected: 50)

#<Thread:0x0000000103170468 run> terminated with exception (report_on_exception is true):
actor-supervisor.rb:11:in `block (2 levels) in initialize': Crash! (RuntimeError)
        from actor-supervisor.rb:4:in `loop'
        from actor-supervisor.rb:4:in `block in initialize'
<internal:ractor>:698:in `take': thrown by remote Ractor. (Ractor::RemoteError)
        from actor-supervisor.rb:28:in `balance'
        from actor-supervisor.rb:77:in `<main>'
actor-supervisor.rb:11:in `block (2 levels) in initialize': Crash! (RuntimeError)
        from actor-supervisor.rb:4:in `loop'
        from actor-supervisor.rb:4:in `block in initialize'

```

*Uh, oh!* No momento de buscar o saldo, o ator já não está mais saudável. Houve um crash que o deixou em estado de erro.&#x20;

Precisamos então *supervisionar* este ator, de modo a garantir que após o crash, o **funcionamento do ator continue normalmente**.

### Supervisionando um ator

Reforçando a implementação atual de `Account` :&#x20;

{% code lineNumbers="true" %}

```ruby
class Account 
  def initialize(balance)
    @actor = Ractor.new(balance) do |balance|
      loop do
        message = Ractor.receive

        case message
        in deposit: amount  then balance += amount
        in withdraw: amount then balance -= amount
        in :balance         then Ractor.yield({ balance: balance })
        in :crash           then raise "Crash!"
        end
      end
    end
  end

  def deposit(amount)
    @actor.send({ deposit: amount })
  end

  def withdraw(amount)
    @actor.send({ withdraw: amount })
  end

  def balance
    @actor.send(:balance)
    @actor.take[:balance]
  end

  def simulate_crash!
    @actor.send(:crash)
  end
end
```

{% endcode %}

Para supervisionar um ator, **precisamos de outro ator.** Se formos pensar no funcionamento deste supervisor, podemos concluir que:

* o supervisor é um ator, que:
  * cria um worker que será supervisionado
  * fica em loop "monitorando" o worker:
    * se está OK, salva o estado do worker e o coloca na outbox
    * se não está OK, cria outro worker com o estado atual do worker que falhou

<figure><img src="/files/M9pSMtJASTjVNnO6S0qq" alt="" width="563"><figcaption></figcaption></figure>

E o processo principal, tudo o que precisa é buscar na **outbox** do supervisor uma conta para poder manipular. Usando, *again*, OOP, conseguimos atingir isto de forma muito intuitiva:

```ruby
class AccountSupervisor
  def initialize
    @actor = Ractor.new do
      # Define o estado inicial (que é o atual também)
      #  e uma conta que será o "worker" supervisionado
      current_balance = 0
      account = Account.new(current_balance)

      loop do
        begin
          # Verifica se o worker está saudável
          response = account.ping
        
          if response && response[:msg] == 'PONG'
            # Se estiver saudável, atualiza o estado atual do worker no supervisor
            # e coloca o worker na caixa de saída (outbox)
            current_balance = response[:balance]
            Ractor.yield(account)
          else 
            # Caso contrário, lança um erro (não vamos tratar este erro por enquanto)
            raise "Account is not responding"
          end
        rescue Ractor::RemoteError, Ractor::ClosedError => e
          # Caso o worker não esteja saudável, como por exemplo sofreu um _crash_, 
          #  cria um novo worker utilizando o estado atual
          puts "[Supervisor] Account crashed with error: #{e.message}. Restarting..."
          account = Account.new(current_balance)
        end
      end
    end
  end

  # Método público que vai ser utilizado pelo processo princial para
  #  manipular uma conta (worker)
  def account
    @actor.take
  end
end
```

Precisamos implementar o método `ping` no worker e também adicionar a mensagem no matching de mensagens da inbox:

```ruby
class Account ......

case message
in deposit: amount  then balance += amount
in withdraw: amount then balance -= amount
in :balance         then Ractor.yield({ balance: balance })
in :crash           then raise "Crash!"
in :ping            then Ractor.yield({ msg: 'PONG', balance: balance })
```

```ruby
class Account .....

def ping
  @actor.send(:ping)
  @actor.take
end
```

<figure><img src="/files/1zA1vVyxXCQVUVrQELqk" alt="" width="563"><figcaption><p>representação final do ator <em>Account</em></p></figcaption></figure>

Agora, só falta ligar tudo, criando o worker a partir do **supervisor**:

```ruby
supervisor = AccountSupervisor.new
supervisor.account.deposit(100)
supervisor.account.withdraw(50)

puts "Balance is: #{supervisor.account.balance} (expected: 50)"
```

Até aqui tudo normal. Na saída conseguimos ver `Balance is: 50 (expected: 50)` .&#x20;

A seguir, simulamos um crash no worker:

```ruby
supervisor.account.simulate_crash!
```

*So far, so good*. Vamos fazer mais um depósito de *200*, pelo que queremos que o worker tenha sido reiniciado pelo supervisor e que ao final tenha o saldo atualizado para 250 (50 anteriormente + 200 de agora):

```ruby
supervisor.account.deposit(200)
puts "Balance is: #{supervisor.account.balance} (expected: 250)"
```

```
Balance is: 50 (expected: 50)

#<Thread:0x0000000104e4fd18 run> terminated with exception (report_on_exception is true):
actor-supervisor.rb:11:in `block (2 levels) in initialize': Crash! (RuntimeError)
        from actor-supervisor.rb:4:in `loop'
        from actor-supervisor.rb:4:in `block in initialize'
        
[Supervisor] Account crashed with error: thrown by remote Ractor.. Restarting...
Balance is: 250 (expected: 250)
```

Após o crash, podemos ver que o worker foi reiniciado e o novo depósito funcionou, sendo somado com o saldo anterior do ator que falhou.

*How cool is that?*

***

## Está gostando deste trabalho?

Se está gostando e considera colaborar para que mais trabalhos assim sejam feitos, não deixe de entrar para a [lista de **investidores premium**](https://concorrencia101.leandronsp.com/agradecimentos).&#x20;

> "Tá de sacanagem Leandro kk"

É sério, qualquer apoio financeiro via PIX é mais que bem-vindo, inclusive compartilhar o guia é uma excelente forma de apoiar o projeto também!

<figure><img src="/files/FUUkoZLpU19gvljXPbr1" alt="" width="283"><figcaption></figcaption></figure>

Ou copia e cola:

```
00020126850014BR.GOV.BCB.PIX013638ee4bde-574b-4197-b10f-68742087b00b0223Gratidão pelo cafezinho5204000053039865802BR5925Leandro Freitas Maringolo6009SAO PAULO62140510qrN6Ov1wRl63041A3C
```

No próximo tópico, vamos entrar no mundo do I/O não-bloqueante em Ruby. Apertem os cintos, pois a jornada ainda está longe do fim.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://concorrencia101.leandronsp.com/parte-ii-concorrencia-em-diferentes-linguagens/concorrencia-em-ruby/modelo-de-atores.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
