Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Processes

11-processes.livemd

Processes

Lets create a simple process

We can create a process with spawn

spawn(fn -> 1 + 2 end)

As you can see, it retuened a value with PID

this is commonly called a pid - process id it can be used to identify the process inside same machine and if connected, between different machines(nodes) as well

pid = spawn(fn -> 1 + 2 end)

We can check process is alive with Process.alive?/1

Process.alive?(pid)

Current Process

Since everything in elixir runs inside a process, what about now?

Yeah, you can get current process with self/0

self()
Process.alive?(self())

Hello Houston?

Lets send a message to a process

send(self(), {:hello, "world"})

Now when we send this, it goes into the mailbox ๐Ÿ“ช of process

Once we have a receive block, it can go through this mailbox and process messages with pattern matching

receive do
  {:hello, msg} -> msg
  {:world, _msg} -> "won't match"
end

Earlier we sent message to self

We can send messages between processes

parent = self()
spawn(fn -> send(parent, {:hello, self()}) end)
receive do
  {:hello, pid} -> "Got hello from #{inspect pid}"
end

Now we can see, the parent got a message from a child

> If there are no messages, receive will wait until a message arrives at mailbox ๐Ÿ“ช

Linking Processes

Most of the time in Elixir, we are linking processes with each other.

This way we can monitor state of child processes and take necessary actions when they fail

spawn(fn -> raise "oops" end)

Lets monitor this process on parent

We can use spawn_link/1 to create a linked process with parent

if child process dies, the parent dies too

fun = fn ->
  pid = spawn(fn ->
    IO.inspect("Parent is alive! Starting database query...")
    spawn_link(fn -> 
      IO.inspect("๐Ÿ‘ถ Child attempting DB connection... #{inspect(self())}")
      raise "DB Connection Failed!"
    end)
    :timer.sleep(1)
    IO.inspect("Parent won't reach this - crashed with child")
  end)
  IO.inspect("Started DB worker at #{inspect(pid)}")
  :timer.sleep(1)
  IO.inspect("Main process still running ๐Ÿš€")
end
fun.()

Instead of directly using spawn, spawn_link we can use Task module for better error handling and monitioring

Task.start(fn -> raise "oops" end)

Building a simple KV Store

With processes and message passing we can actually hold a state

defmodule KV do
  def start_link do
    Task.start_link(fn -> loop(%{}) end)
  end

  defp loop(map) do
    receive do
      {:get, key, caller} ->
        send(caller, Map.get(map, key)) # send the item to caller
        loop(map) # loop again with same state
      {:put, key, value} ->
        loop(Map.put(map, key, value)) # update the state and loop again
    end
  end
end
{:ok, pid} = KV.start_link()
send(pid, {:get, :hello, self()})

Lets get the message on parent

receive do
  msg -> IO.inspect(msg)
end

Lets add something to the KV store

send(pid, {:put, :hello, :world})

and get it back

send(pid, {:get, :hello, self()})

Now we listen for message

receive do
  msg -> IO.inspect(msg)
end

Agents

Agents abstract above concept and gives you a nice and simple abstraction

Start the agent

{:ok, agent} = Agent.start_link(fn -> [] end)

Update a value inside the list

Agent.update(agent, fn list -> ["eggs" | list] end)

Get it back

Agent.get(agent, fn list -> list end)

Usage of Agent is bit uncommon in Elixir

We have other commonly used tools like GenServer