README.md

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

# Periodic: run functions at intervals [![Build Status](https://travis-ci.org/pragdave/periodic.svg?branch=master)](https://travis-ci.org/pragdave/periodic)


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

* `{ :ok, new_state }` to have itself rescheduled with a (potentially)
  updated state.

* `{ :change_interval, new_interval, new_state }` to reschedule itself
  with a new state, but updating the interval betweeen schedules.

* `{ :stop, :normal }` to exit gracefully.

* any other return value will be treated as an error.\

All intervals are specified in milliseconds.

## What does it look like?

mix.exs:

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

application.ex

~~~ elixir
child_spec = [
  Periodic,
  MyApp,
  . . .
]
~~~

First a silly example:

~~~ elixir
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.

~~~ elixir
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:

* In the real world you'd likely split this into multiple modules.

* The parameters to the first call to `Periodic.repeat` say run
  `Fetcher.fetch` every 30s, passing it a map containing the name
   of a feed and the pid to send the data to.

* the second call to `Fetcher.fetch` sets up a second schedule. This
  happens to call the same function, but every 60s. It also offsets
  the time of these calls (starting with the first) by 15s

  This means the timeline for calls to the function will be:

  | time from start | call                          |
  |-----------------|-------------------------------|
  |   +0s           | fetch{feed: Stocks, ...}      |
  |   +15s          | fetch{feed: Bonds,  ...}      |
  |   +30s          | fetch{feed: Stocks, ...}      |
  |   +60s          | fetch{feed: Stocks, ...}      |
  |   +75s          | fetch{feed: Bonds,  ...}      |
  |   +90s          | fetch{feed: Stocks, ...}      |
  |   +120s         | fetch{feed: Stocks, ...}      |
  |   +135s         | fetch{feed: Bonds,  ...}      |
  |    . . .        |                               |

* The `fetch` function gets data for the appropriate feed, and then
  calls back into the original module, passing the pid of the genserver,
  the name of the feed and the data.

* The `handle_data` function it calls just forwards the request on to
  the genserver.

  (Technically the call to `GenServer.cast` could have been made
  directly in the `fetch` function, but in our mythical _real world_,
  it's likely the periodically run functions would be decoupled from the
  genserver.

### The API

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

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

* `func_spec` may be an anonymous function of arity 1, a 2-tuple
  containing the name of a module and the name of a function, or just
  the name of the module (in which case the function is assumed to be
  named `run/1`.

* The `interval` specifies the number of milliseconds between executions
  of the function. `Periodic` makes some attempt to minimize drift of
  this timing, but you should treat the value as approximate: you'll see
  some spreading of the interval timing of perhaps a millisecond on some
  iterations.

* The options list make contain:

  * `state: ` _term_

    The initial state that is passed as a parameter when the function is
    first executeded.

  * `name:` _name_

    A name for the task. This can be used subsequently to terminate it.

  * `offset:` _ms_

    An offset (in milliseconds) to be applied before the first execution
    of the function. This can be used to stagger executions of multiple
    sets of periodic functions if their intervals would otherwise cause
    them to execute at the same time.

You can remove a previously added periodic function with

~~~ elixir
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:

~~~ elixir
@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

* `Periodic` is a `DynamicSupervisor` which should be started by one of
  your application's own supervisors.

* Each call to `Periodic.repeat` creates a new worker process. This
  worker spends most of its time waiting for the interval timer to
  trigger, at which point it invokes the function you passed it, then
  resets the timer.

* If a function takes more time to execute than the interval time, then
  the next call to that function will happen immediately, and all
  subsequent calls to it will be timeshifted by the overrun.


> See [license.md](license.md) for copyright and licensing information.