<img align="right" width="200" title="logo: a clocks" src="./assets/images/clock.svg">

Periodic: run functions at intervals Build Status

The Periodic supervisor manages a dynamic set of tasks. Each of these tasks is run repeatedly at a per-task specified interval.

A task is repreented as a function. It receives a single parameter, its current state. When complete, this function can return

All intervals are specified in milliseconds.

What does it look like?

mix.exs:

deps: { :periodic, ">= 0.0.0" },

application.ex

child_spec = [
  Periodic,
  MyApp,
  . . .
]

First a silly example:

defmodule Silly do
  use GenServer

  def callback(state = [{ label, count }]) do
    IO.inspect state
    { :ok, [ { label, count + 100 }]}
  end

  def start_link(_) do
    GenServer.start_link(__MODULE__, nil)
  end

  def init(_) do
    Periodic.repeat({ __MODULE__, :callback }, 500, state: [ one: 1 ])
    Periodic.repeat({ __MODULE__, :callback }, 300, state: [ two: 2 ], offset: 100)
    { :ok, nil }
  end

end

The calls to Periodic.repeat will cause the callback function to be called in two different sequences: the first time it will be called every 500ms, and it will also be called every 300ms. Each sequence of calls will maintain its own state.

This will output:

Compiling 1 file (.ex)
[one: 1]
[two: 2]
[two: 102]
[one: 101]
[two: 202]
[one: 201]
[two: 302]
[two: 402]
 . . .

As something more complex, here's a genserver that fetches data from two feeds. The first is fetched every 30 seconds, and the second every 60s.

defmodule Fetcher do
  use GenServer

  def start_link(_) do
    GenServer.start_link(__MODULE__, nil)
  end

  def init(_) do
    { :ok, _ } = Periodic.repeat({ __MODULE__, :fetch }, 30_000,
                                 state: %{ feed: Stocks, respond_to: self() })

    { :ok, _ } = Periodic.repeat({ __MODULE__, :fetch }, 60_000,
                                 state: %{ feed: Bonds, respond_to: self() }, offset: 15_000)
    { :ok, %{} }
  end

  # this function is run by the two task runners created in `init/1)`. They
  # fetch data from the feed whose name is in the state, and then send the
  # result back to the original server

  def fetch(task) do
    data = task.feed.fetch()
    Fetcher.handle_data(task.respond_to, task.feed, data)
    { :ok, state }
  end

  # and this function forwards the feed response on to the server
  def handle_data(worker_pid, feed, data) do
    GenServer.cast(worker_pid, { incoming, feed, data })
  end

  def handle_cast({ :incoming, Stocks, data }, state) do
    ## ...
  end

  def handle_cast({ :incoming, Bonds, data }, state) do
    ## ...
  end
end

Notes:

The API

To cause a function to be invoked repeatedly every so many milliseconds, use:

{ :ok, pid } = Periodic.repeat(func_spec, interval, options \\ [])

You can remove a previously added periodic function with

Periodic.stop_task(pid)

where pid is the value returned by repeat/3

The Callback Function

You write functions that Periodic will call. These will have the spec:

@typep state :: term()
@typep periodic_callback_return ::
    { :ok, state() }                                       |
    { :change_interval, new_interval::integer(), state() } |
    { :stop, :normal }                                     |
    other :: any()

@spec periodic_callback(state :: state()) :: periodic_callback_return()

Runtime Charactertics

See license.md for copyright and licensing information.