Skip to content
This repository
branch: master
README.md

ExReminder

Important notice Elixir is still being actively developed, so the code in this tutorial might break. If it doesn't work for you, please file an issue or send a note to the mailing list. Thanks for understanding :)

This is a simple client-server application demonstrating the power of the Erlang VM and the cuteness of Elixir. Based on this chapter from the awesome Learn You Some Erlang for Great Good! book.

If you have just finished the Getting Started guide, you should first take a look at this chat demo. It is probably the simplest client-server app that can be written in Elixir. Try playing with it for a while until you feel comfortable enough writing you own modules and functions.

In this tutorial I'm going to guide you through the code of a slightly more advanced application which also implements a client-server model. I'm expecting that you are familiar with Erlang's core concepts such as processes and message passing. If you're new to the Erlang/OTP ecosystem, take a look at the following section where you'll find pointers to a number of helpful online resources.

If you are already familiar with Erlang and feel confident enough to get your hands dirty with some Elixir code, you may safely skip the next section and jump directly to First Things First. (Although you might still find the crash course on Erlang syntax useful, as it compares Erlang snippets with corresponding Elixir code.)

Running examples

A couple of examples are provided to demonstrate the usage of the API we're going to build. The best way to run them is to start iex and paste the code into it. If you try to run them like so

elixir test_event.exs

you might get unexpected results.

Erlang

Be sure to look at the section called A Byte of Erlang in this chapter of the Getting Started guide if you haven't got a chance to play with Erlang before. The present tutorial assumes you have familiarity with Erlang's basic concepts like processes, receive loops, message passing, etc. Knowledge of OTP is not required, though.

If you're looking at this and start feeling discouraged, please don't! After all, you can skip the theory and dive straight into the code. You are free to take any approach you wish as long as you're enjoying the process. Remember that in case of any difficulties, you can always visit the #elixir-lang channel on irc.freenode.net or send a message to the mailing list. I can assure you, there will be someone willing to help.

First Things First

Before writing a single line of code, we need think a little about the problem we're facing and the goals we're trying to achieve. Refer to the aforementioned chapter and read the first couple of sections where you'll find a detailed description (with pictures!) of the architecture and messaging protocol for our application. As soon as you've got a basic understanding of the problem and the proposed design for solving it, come back here and we shall start our walk through the code.

The Event Module

We'll start with the Event module. In our application, when a client asks the server to create an event, the server spawns a new Event process which then waits for the specified amount of time before it calls back to the server which then forwards the event's name back to the client.

defmodule Event do
  defrecord State, server: nil, name: "", to_go: 0

  ## Public API ##

  def start(event_name, delay) do
    :erlang.spawn __MODULE__, :init, [Process.self, event_name, delay]
  end

  def start_link(event_name, delay) do
    :erlang.spawn_link __MODULE__, :init, [Process.self, event_name, delay]
  end

  def init(server, event_name, delay) do
    main_loop State.new server: server, name: event_name, to_go: datetime_to_seconds(delay)
  end

First, we define a record type named Event.State. In it we will store all the state required for the event to run and contact the server when its time has run out. Notice how the record type automatically inherits the name from its parent module, so a nested record type Record in module Module will always have the name Module.Record when used outside of the module. But for internal use it can be referenced by its local name which in our case is simply State.

Elixir's Process module contains functions that are commonly used when dealing with processes. Functions such as linking to a process, registering a process, creating a monitor, getting access to the process' local dictionary — all of those live in the Process module, the documented source code for which is available here.

The first three functions are responsible for spawning a new event process and initializing the state with the data provided by the caller. Here we call Erlang's spawn and spawn_link functions directly purely for demonstrational purposes. Elixir provides equivalent built-in functions.

__MODULE__ is one of Elixir's read-only pseudo-variables. Similarly to Erlang's ?MODULE, it expands to the current module's name at compile time. The other pseudo-variables in Elixir are

  • __FUNCTION__ — returns a tuple representing the current function by name and arity or nil;
  • __LINE__ — returns an integer representing the current line;
  • __FILE__ — returns a string representing the current file;
  • __MAIN__ — the main namespace where modules are stored. For instance, List can also be accessed as __MAIN__.List;
  • __LOCAL__ — works as a proxy to force a function call to resolve locally (and not be expanded as a macro).

In the init function we create a new State record passing initial values as an orddict. If you prefer a more formal syntax, you could rewrite it in one of the following ways:

State.new [server: server, name: event_name, to_go: delay]
# or
State.new([server: server, name: event_name, to_go: delay])

Note, however, that you cannot pass a list of tuples, because new expects an orddict (which is an ordered list of tuples). When it doesn't introduce ambiguity, it is recommended to use the first approach.

The last thing of note here is the fact that we call the datetime_to_seconds function passing it the given delay. This is done in order to accept the delay both in seconds as well as in Erlang's datetime format. You can find the definition of the datetime_to_seconds function at the end of the Event module.


Next, we have a function for cancelling an event.

  def cancel(pid) do
    # Create a monitor to know when the process dies
    mon = Process.monitor pid
    pid <- { Process.self, mon, :cancel }
    receive do
    match: { ^mon, :ok }
      Process.demonitor mon, [:flush]
      :ok
    match: { :DOWN, ^mon, :process, ^pid, _reason }
      # The server is down. We're ok with that.
      :ok
    end
  end

This is done by sending a :cancel message to the event process which is then received in the main loop. If we look closely at the main_loop function below, we'll see that all it does is hang waiting for the :cancel message to arrive. Once it receives the message, it simply returns :ok and exists the loop, thus terminating the process.

We use a left-arrow operator <- to send a message (Erlang uses ! for the same purpose). Also note the use of the caret ^ symbol. When you do pattern matching in Elixir and you want to match against the value of a variable (rather than bind the variable to a new value), prepend the variable's name with a caret.


The last function in our Event module is the main loop of the process.

  defp main_loop(state) do
    server = state.server
    receive do
    match: {^server, ref, :cancel}
      server <- { ref, :ok }

    after: state.to_go * 1000
      server <- { :done, state.name }
    end
  end

It is not actually a loop, strictly speaking, but you get the point. Every Event process spends most of its lifetime in this function. Other than the fact that we use defp instead of def to keep this function private to the Event module, there is nothing new in this particular piece of code.

Testing The Event Module

Notice how our Event module doesn't depend on the server module or any other module for that matter. All it does is provide an interface for spawning new event processes and cancelling them. This approach makes it easy to test the Event module in isolation and make sure eveything works as expected.

Before running the code, we need to compile it. I have provided a Makefile for convenience. Simply execute make from the project's root to compile the source code for our modules.

Once the code is compiled, launch iex inside the project's directory, then open the test_event.exs file and paste its contents into the running Elixir shell. Make sure everything is working as expected: the iex process successfully receives a { :done, "Event" } message from the first spawned event process. Then we create another event with a larger timeout value and cancel it before the timeout runs out. Play around with it for a while, spawning multiple events and using the provided flush function to check that you receive reminders from the spawned events.

Once you're satisfied with the result, move on to the next section where we're going to take a closer look at the event server.

The EventServer Module

The EventServer module will be responsible for creating events and notifying subscribed clients when an event is ready to be delivered. Don't forget to keep a tab with the Erlang book open alongside this tutorial, it contains a detailed explanation of the decisions we're making while writing the code for the server.

Similarly to the Event module, our server will need to keep some state in order to be of any use. Here we defined two record types: EventServer.State and EventServer.Event:

defmodule EventServer do
  # We need to keep a list of all pending events and subscribed clients
  defrecord State, events: [], clients: []

  # Event description
  defrecord Event, name: "", description: "", pid: nil, timeout: 0

In the init function, we're entering the main loop passing it a State record. Here we're exploiting Elixir's support for default arguments in functions to create a new state when init is called without arguments.

  def init(state // State.new) do
    main_loop state
  end

The next couple of functions in EventServer don't introduce new concepts, they simply wrap the messaging protocol used by the server in a neat API, so we'll skip them. One thing I'd like to point out though, in the listen function below, is that we can prepend argument and variable names with underscore _. Because some of the variables are not used inside the match body, the compiler would emit a warning if those variables did not start with underscore. Alternatively, we could use a single underscore in place of a variable name to ignore it completely.

  def listen(delay) do
    receive do
    match: m = { :done, _name, _description }
      [m | listen(0)]
    after: delay * 1000
      []
    end
  end

Now, let's take a look at the server's main loop which is pretty large, although its basic structure is rather simple. Here's what its skeleton looks like:

  def main_loop(state) do
    receive do
    match: { pid, msg_ref, {:subscribe, client} }
      # Subscribe a client identified by the `pid`
      # ...
      pid <- { msg_ref, :ok }
      main_loop new_state

    match: { pid, msg_ref, {:add, name, description, timeout} }
      # Spawn a new event process to handle the :add request from client
      # ...
      pid <- { msg_ref, :ok }
      main_loop new_state

    match: { pid, msg_ref, {:cancel, name} }
      # Tear down the event process corresponding to `name`
      # ...
      pid <- { msg_ref, :ok }
      main_loop new_state

    match: { :done, name }
      # The event has finished, notify all clients
      # ...
      main_loop new_state

    match: :shutdown
      # Shut down the server and all living event processes
      exit :shutdown

    match: { :DOWN, ref, :process, _pid, _reason }
      # A client has crashed. Remove it from our subscribers list.
      # ...
      main_loop new_state

    match: :code_change
      # New code has arrived! Time to upgrade.
      #
      # The upgrade process is performed by using the qualified name
      # __MODULE__.init. Calling 'main_loop' instead would continue running the
      # old code. We can't to a call __MODULE__.main_loop, because 'main_loop'
      # is a private function. For this reason, we're doing a recursive call
      # through the 'init' function.
      __MODULE__.init state

    match: else
      # Someone sent us a message we don't understand
      IO.puts "Unknown message: #{inspect else}"
      main_loop state
    end

The basic pattern is as follows: enter the receive block waiting for a message. Once a message has arrived, perform appropriate actions and make a recursive call with the updated state into the same message loop. To see exactly what actions are being performed and why, read carefully through the explanation in the book and take a look at the code in the event_server.ex file.

In the source code for our server you'll find another example of using Erlang modules — we use the orddict module for book-keeping of clients and events. Elixir currently provides the Keyword module that can only have atoms as keys, so we're better off using Erlang's native orddict module for the time being.


Lastly, we have a private function that broadcasts a message to all subscribed clients:

  # Send 'msg' to each subscribed client
  def send_to_clients(msg, clients) do
    Enum.map clients, fn({_ref, pid}) ->
      pid <- msg
    end
  end

Enum is a new module in Elixir, it provides common functions that deal will collections such as map, filter, all?, split, etc. Take a look at its source code which is heavily documented.

Testing The Server

As with the Event module, I've written a small test-script to check that the server works properly. It is located in the test_server.exs file. As before, start up iex in the project's directory and copy the file contents into the shell.

The next step you might take is walk through the code yourself, it is abundantly commented. Every time you stumble upon an unfamiliar concept, try playing with it in the shell and see what happens.

There is also a rudimentary supervisor implemented in the event_sup.ex file that can launch the server as its child process and restart it in case of a crash.

Hot Code Swapping

One more thing I'd like to mention is how we can test hot code swapping in a running application. Compile the source code as before and start up iex in the project's root directory. Copy the contents of the test_server.exs file into the shell once again so that we have a server instance running.

Now open another terminal tab or window and navigate to the project's root. Make some change in the code, for instance, change the message the server sends in response to an :add request. This is line 120 in the event_server.ex file. Here's what mine looks like after the change:

pid <- { msg_ref, :sir_yes_sir }

Then you need to recompile the source by invoking

make

Now go back to the Terminal tab you have iex running in and evaluate the following expressions:

# Ask Erlang to reload our EventServer module
:code.load_file EventServer
#=> {:module,EventServer}

EventServer.add_event "1", "", 1000
#=> :ok

# The new code is now loaded, but our server process is still running the old
# one. We need to tell it that it should make a qualified call to the `main_loop`
# function in order to upgrade to the newest available version of the module.
EventServer <- :code_change
#=> :code_change

# Make sure the code has been reloaded
EventServer.add_event "New event", "No description", 100
#=> :sir_yes_sir

That's it! You have just successfully updated the code of a running program. Wasn't it fun?

Where to go Next

Congratulations! You now have quite a solid understanding of what it takes to write a full-blown client-server application using Elixir. Now you're ready to start working on your own project or join efforts with the community and help out a project in need. Visit the #elixir-lang channel on irc.freenode.net and join the mailing list to keep in touch.

Good luck and have fun!

Something went wrong with that request. Please try again.