e2 Tasks

A task is an e2 service that primarily performs a single thread of work.

A task can repeat if it needs to perform the work again.

Here’s a simple task that looks up a value in a database and logs the result.

-module(db_read_task).

-behavior(e2_task).

-export([start_link/1, handle_task/1]).

start_link(Db) ->
    e2_task:start_link(?MODULE, Db).

handle_task(Db) ->
    Value = db:read_value(Db, "special_value"),
    e2_log:info({db_read_result, Value}),
    {stop, normal}.

Tasks are often implemented as “fire and forget” – you start them and they operated autonomously until they finish the work, or crash.

Tasks are typically started and supervised by task supervisors. Task supervisors automatically remove tasks when they’re done.

Starting Tasks

Start a task using e2_task:start_link/3.

Initial State

Like e2_service, a task’s state is initialized in one of two ways:

  • If the task exports init/1, the result of init/1 determines the task state (the second argument to e2_task:start_link/3 is the argument to init/1).

  • If the task does not export init/1, its initial state is the second argument used in the e2_task:start_link/3.

Repeating Tasks

Tasks can be configured to repeat. There are generally two scenarios where a task is repeating:

  • The task should run at regular intervals – for example, a task that routinely checks for problems to fix

  • The task should continually loop – for example, a server that waits for new connections, dispatching them to handlers

Here’s a task that scans a directory for invalid files every 30 seconds:

-module(dir_cleanup_task).

-export([start_link/1, handle_task/1]).

start_link(Dir) ->
    e2_task:start_link(?MODULE, Dir, [{repeat, 30000}]).

handle_task(Dir) ->
    util:delete_files(util:find_invalid_files(Dir)),
    {repeat, Dir}.

This task will repeat forever. If wanted to stop at some point, it could return {stop, Reason} from handle_task/1.

Delayed Tasks

By default, handle_task/1 is called as soon as the task process is started. A task can alternatively be delayed.

Here’s how a task can be started with an initial delay of 5 seconds:

e2_task:start_link(?MODULE, Args, [{delay, 5000}]).

Examples

The examples/tasks project illustrates various task uses.

Here’s a task that is used in examples/calc to handle client connections:

-module(calc_handler).

-behavior(e2_task).

-export([start_link/1]).

-export([handle_task/1, terminate/2]).

%%%-------------------------------------------------------------------
%%% API
%%%-------------------------------------------------------------------

start_link(Socket) ->
    e2_task:start_link(?MODULE, Socket).

%%%-------------------------------------------------------------------
%%% Task callback
%%%-------------------------------------------------------------------

handle_task(Socket) ->
    gen_tcp:send(Socket, <<"> ">>),
    case gen_tcp:recv(Socket, 0) of
        {ok, <<"q\r\n">>} ->
            {stop, normal};
        {ok, <<"quit\r\n">>} ->
            {stop, normal};
        {ok, Data} ->
            case calc:eval(Data) of
                {ok, Result} ->
                    gen_tcp:send(Socket, format_result(Result)),
                    gen_tcp:send(Socket, <<"\n">>);
                {error, Err} ->
                    gen_tcp:send(Socket, <<"ERROR: ">>),
                    gen_tcp:send(Socket, format_error(Err)),
                    gen_tcp:send(Socket, <<"\n">>)
            end,
            {repeat, Socket};
        {error, closed} ->
            {stop, normal};
        {error, Err} ->
            {stop, Err}
    end.

terminate(_Reason, Socket) ->
    gen_tcp:close(Socket).

%%%-------------------------------------------------------------------
%%% Internal functions
%%%-------------------------------------------------------------------

format_result(F) when is_float(F) ->
    iolist_to_binary(io_lib:format("~f", [F]));
format_result(I) when is_integer(I) ->
    list_to_binary(integer_to_list(I)).

format_error(Err) ->
    try iolist_to_binary(Err) of
        Bin -> Bin
    catch
        error:badarg ->
            iolist_to_binary(io_lib:format("~p", [Err]))
    end.