Job scheduling
in Elixir

Constantin Rack

@ConstantinRack | github.com/c-rack

elixir.ruhr | 11.02.2016 | Bochum, Germany

Problem

How to execute tasks periodically?

Backups, health checks, newsletters, cleanup, …

Solutions

Erlang / Elixir Process

Process Communication

Supervision

Start from scratch


            defmodule PeriodicTask do
            
              use GenServer
            
              def init(state) do
                Process.send_after(self, :tick, 1000)
                {:ok, state}
              end
            
              def handle_info(:tick, state) do
                Process.send_after(self, :tick, 1000)
                IO.puts "Executing every second"
                {:noreply, state}
              end
            
            end

            # GenServer.start_link(PeriodicTask, nil)
          

Demo

Simple periodic task

Recap: cron expressions

Meaning Allowed Examples
Minute 0 - 59 * | 0 | 0-5 | 0,30 | */15
Hour 0 - 23 * | 0 | 0-5 | 0,5 | */2
Day of month 1 - 31 * | 1 | 1-5 | 1,5 | */2
Month 1 - 12 * | 1 | 1-5 | 1,5 | jan-may
Day of week 0 - 6 * | 0 | 1-5 | 1,5 | mon-fri

Demo

quantum

Components

  • executor
  • job
  • matcher
  • normalizer
  • parser
  • timer
  • translator

Beautiful Elixir #1

Normalize cron expressions


  defmodule Quantum.Translator do

    def translate(s) do
      {s, _} = List.foldl ~w{sun mon tue wed thu fri sat}, {s, 0},
               fn n, acc -> do_translate acc, n end
      s
    end

    defp do_translate({s, i}, n) do
      {String.replace(s, n, "#{i}"), i + 1}
    end

  end
  
  # iex(1)> "mon-fri" |> Quantum.Translator.translate
  # "1-5"
          

Beautiful Elixir #2

Macros

  defmodule Quantum.Timer do

    now = case Application.get_env(:quantum, :timezone, :utc) do
      :utc ->     &:calendar.now_to_universal_time/1
      :local ->   &:calendar.now_to_local_time/1
      timezone -> raise "Unsupported timezone: #{timezone}"
    end

    def tick do
      {d, {h, m, s}} = unquote(now).(:os.timestamp)
      Process.send_after(self, :tick, (60 - s) * 1000)
      {d, h, m}
    end

  end
          

Thanks!

@ConstantinRack | github.com/c-rack