/core

Library for selective receive OTP processes

Primary LanguageElixirApache License 2.0Apache-2.0

Core

Library for implementing OTP processes natively in Elixir.

Provides utility functions and macros to implement 100% OTP compliant processes with 100% compatibility with all Erlang/OTP modules and tools.

Installing

git clone https://github.com/fishcakez/core.git
cd core
mix do deps.get, docs, compile

Hello World

Start a process that prints "Hello World" to :stdio.

defmodule HelloWorld do

  use Core

  def start_link(), do: Core.start_link(__MODULE__, nil)

  def init(_parent, _debug, _args) do
    IO.puts("Hello World")
    # Core.init_ack/0 will cause start_link/0 to return { :ok, self() }. If this
    # function is never called start_link will block until this process exits.
    Core.init_ack()
    exit(:normal)
  end

end

Features

  • Asynchronous and synchronous process initiation (with name registration).
  • Automatic logging of un-rescued exceptions.
  • System calls that work with any OTP compliant process.
  • Receive macro to handle system messages.
  • Supports progressive enhancement of OTP features: system message automatically handled until you want to change the default behaviour.

Basic Ping Server

Starts a process that can be pinged.

defmodule PingPong do

  use Core

  @spec ping(Core.t) :: :pong
  def ping(process), do: Core.call(process, __MODULE__, :ping, 5000)

  @spec count(Core.t) :: non_neg_integer
  def count(process), do: Core.call(process, __MODULE__, :count, 5000)

  @spec close(Core.t) :: :ok
  def close(process), do: Core.call(process, __MODULE__, :close, 5000)

  @spec start_link() :: { :ok, pid }
  def start_link() do
    Core.start_link(__MODULE__, nil)
  end

  # Core api

  def init(_parent, _debug, _args) do
    Core.init_ack()
    loop(0)
  end

  ## Internal

  defp loop(count) do
    receive do
      { __MODULE__, from, :ping } ->
        Core.reply(from, :pong)
        loop(count + 1)
      { __MODULE__, from, :count } ->
        Core.reply(from, count)
        loop(count)
      { __MODULE__, from, :close } ->
        Core.reply(from, :ok)
        terminate(:normal)
    end
  end

  defp terminate(reason) do
    exit(reason)
  end

end

Advanced Ping Server

Starts a process that can be pinged, live debugged and live code upgraded.

For example Core.Sys.set_state(pid, 0) will reset the count to 0.

defmodule PingPong do

  use Core.Sys

  @spec ping(Core.t) :: :pong
  def ping(process), do: Core.call(process, __MODULE__, :ping, 5000)

  @spec count(Core.t) :: non_neg_integer
  def count(process), do: Core.call(process, __MODULE__, :count, 5000)

  @spec close(Core.t) :: :ok
  def close(process), do: Core.call(process, __MODULE__, :close, 5000)

  # die/1 will print alot of information because the exit reason is abnormal.
  @spec die(Core.t) :: :ok
  def die(process), do: Core.call(process, __MODULE__, :die, 5000)

  @spec start_link() :: { :ok, pid }
  def start_link() do
    Core.start_link(nil, __MODULE__, nil,
      [{ :debug, [{ :log, 10 }, { :stats, true }] }])
  end

  ## Core api

  def init(parent, debug, _args) do
    Core.init_ack()
    loop(0, parent, debug)
  end

  ## Core.Sys (minimal) api

  def system_continue(count, parent, debug), do: loop(count, parent, debug)

  def system_terminate(count, parent, debug, reason) do
    terminate(count, parent, debug, reason)
  end

  ## Internal

  defp loop(count, parent, debug) do
    Core.Sys.receive(__MODULE__, count, parent, debug) do
      { __MODULE__, from, :ping } ->
        # It is not required to record events using `Core.Debug.event/1` but is
        # a useful debug feature that is compiled to a no-op in production.
        debug = Core.Debug.event(debug, { :in, :ping, elem(from, 0) })
        Core.reply(from, :pong)
        debug = Core.Debug.event(debug, { :out, :pong, elem(from, 0) })
        count = count + 1
        debug = Core.Debug.event(debug, { :count, count })
        loop(count, parent, debug)
      { __MODULE__, from, :count } ->
        debug = Core.Debug.event(debug, { :in, :count, elem(from, 0) })
        Core.reply(from, count)
        debug = Core.Debug.event(debug, { :out, count, elem(from, 0) })
        loop(count, parent, debug)
      { __MODULE__, from, :close } ->
        debug = Core.Debug.event(debug, { :in, :close, elem(from, 0) })
        Core.reply(from, :ok)
        debug = Core.Debug.event(debug, { :out, :ok, elem(from, 0)  })
        terminate(count, parent, debug, :normal)
      { __MODULE__, from, :die } ->
        debug = Core.Debug.event(debug, { :in, :die, elem(from, 0) })
        Core.reply(from, :ok)
        debug = Core.Debug.event(debug, { :out, :ok, elem(from, 0)  })
        terminate(count, parent, debug, :die)
    end
  end

  defp terminate(count, parent, debug, reason) do
    event = { :EXIT, reason }
    debug = Core.Debug.event(debug, event)
    Core.stop(__MODULE__, count, parent, debug, reason, event)
  end

end