Current section
Files
Jump to
Current section
Files
src/taskforce.erl
% vim: set expandtab softtabstop=4 shiftwidth=4:
-module(taskforce).
-include("include/taskforce.hrl").
-export([task/3]). -ignore_xref({task,3}).
-export([execute/1]). -ignore_xref({execute,1}).
-export([execute/2]). -ignore_xref({execute,2}).
-opaque task() :: tf_task().
-type tasks() :: #{ TaskId :: term() => Task :: task() }.
-ifndef(pre19).
-type task_settings() ::
#{ timeout := pos_integer() }.
-else.
-type task_settings() ::
#{ timeout => pos_integer() }.
-endif.
-type execution_options() ::
#{ timeout => pos_integer(),
max_workers => pos_integer() }.
-ifndef(pre19).
-type result() ::
#{ completed := #{ TaskId :: term() => TaskResult :: term() },
individual_timeouts := [TaskId :: term()],
global_timeouts := [TaskId :: term()] }.
-else.
-type result() ::
#{ completed => #{ TaskId :: term() => TaskResult :: term() },
individual_timeouts => [TaskId :: term()],
global_timeouts => [TaskId :: term()] }.
-endif.
-export_type([task/0]).
-export_type([tasks/0]).
-export_type([execution_options/0]).
-export_type([result/0]).
%%%%
%% deprecated
-export([new_task/4]). -ignore_xref({new_task,4}).
-export([execute_tasks/1]). -ignore_xref({execute_tasks,1}).
-export([execute_tasks/2]). -ignore_xref({execute_tasks,2}).
-export([execute_tasks/3]). -ignore_xref({execute_tasks,3}).
-deprecated([{new_task,4, next_major_release}]).
-deprecated([{execute_tasks,'_', next_major_release}]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Interface
-spec task(Function, Args, TaskSettings) -> Task
when Function :: fun(),
Args :: [term()],
TaskSettings :: task_settings(),
Task :: task().
task(Function, Args, TaskSettings) ->
#tf_task{ id = implicit_id,
fun_ref = Function,
args = Args,
timeout = maps:get(timeout, TaskSettings) }.
-spec execute(Tasks) -> Result
when Tasks :: tasks(),
Result :: result().
execute(Tasks) ->
execute(Tasks, #{}).
-spec execute(Tasks, ExecutionOptions) -> Result
when Tasks :: tasks(),
ExecutionOptions :: execution_options(),
Result :: result().
execute(Tasks, ExecutionOptions) ->
TaskList = task_list(Tasks),
result( execute_(TaskList, ExecutionOptions) ).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Old interface (deprecated)
%% @hidden
-spec new_task(Id, FunRef, Args, Timeout) -> Task
when Id :: term(),
FunRef :: fun(),
Args :: [term()],
Timeout :: pos_integer(),
Task :: tf_task().
new_task(Id, FunRef, Args, Timeout) ->
#tf_task{id = Id,
fun_ref = FunRef,
args = Args,
timeout = Timeout}.
%% @hidden
-spec execute_tasks(TaskList) -> OldStyleResult
when TaskList :: [Task],
OldStyleResult :: {Completed, IndividualTimeouts, Timeouts},
Completed :: [{TaskId, Task}],
IndividualTimeouts :: [TaskId],
Timeouts :: [TaskId],
Task :: [task()],
TaskId :: term().
execute_tasks(TaskList) ->
old_style_result( execute_(TaskList, #{}) ).
%% @hidden
-spec execute_tasks(TaskList, Timeout) -> OldStyleResult
when TaskList :: [Task],
Timeout :: pos_integer(),
OldStyleResult :: {Completed, IndividualTimeouts, Timeouts},
Completed :: [{TaskId, Task}],
IndividualTimeouts :: [TaskId],
Timeouts :: [TaskId],
Task :: [task()],
TaskId :: term().
execute_tasks(TaskList, Timeout) ->
old_style_result( execute_(TaskList, #{ timeout => Timeout }) ).
%% @hidden
-spec execute_tasks(TaskList, Timeout, MaxWorkers) -> OldStyleResult
when TaskList :: [Task],
Timeout :: pos_integer(),
MaxWorkers :: pos_integer(),
OldStyleResult :: {Completed, IndividualTimeouts, Timeouts},
Completed :: [{TaskId, TaskResult}],
IndividualTimeouts :: [TaskId],
Timeouts :: [TaskId],
Task :: [task()],
TaskId :: term(),
TaskResult :: term().
execute_tasks(TaskList, Timeout, MaxWorkers) ->
old_style_result(
execute_(TaskList, #{ timeout => Timeout,
max_workers => MaxWorkers })).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Internal
shuffle_list(L) ->
KeyedL = [{V, rand_small()} || V <- L],
SortedKeyedL = lists:keysort(2, KeyedL),
[V || {V, _K} <- SortedKeyedL].
-ifdef(pre18).
rand_small() ->
random:uniform(1 bsl 26).
-else.
rand_small() ->
rand:uniform(1 bsl 26).
-endif.
-spec task_list(Tasks) -> TaskList
when Tasks :: #{ TaskId => Task },
TaskList :: [Task],
TaskId :: term(),
Task :: task().
task_list(Tasks) ->
[Task#tf_task{ id = Id }
|| {Id, Task} <- maps:to_list(Tasks)].
-spec max_workers(ExecutionOptions) -> MaxWorkers
when ExecutionOptions :: execution_options(),
MaxWorkers :: pos_integer().
max_workers(ExecutionOptions) ->
case maps:find(max_workers, ExecutionOptions) of
{ok, MaxWorkers} ->
MaxWorkers;
error ->
erlang:system_info(schedulers_online)
end.
-spec global_timeout(TaskList, MaxWorkers, ExecutionOptions) -> GlobalTimeout
when TaskList :: [Task],
MaxWorkers :: pos_integer(),
ExecutionOptions :: execution_options(),
GlobalTimeout :: pos_integer(),
Task :: task().
global_timeout(TaskList, MaxWorkers, ExecutionOptions) ->
case maps:find(timeout, ExecutionOptions) of
{ok, GlobalTimeout} ->
GlobalTimeout;
error ->
lists:sum([Task#tf_task.timeout || Task <- TaskList]) div MaxWorkers
end.
-spec execute_(TaskList, ExecutionOptions) -> BiddingResult
when TaskList :: [task()],
ExecutionOptions :: execution_options(),
BiddingResult :: tf_bidding_result().
execute_(TaskList, ExecutionOptions) ->
MaxWorkers = max_workers(ExecutionOptions),
GlobalTimeout = global_timeout(TaskList, MaxWorkers, ExecutionOptions),
ShuffledTasks = shuffle_list(TaskList),
Bidding = #tf_bidding{ tasks = ShuffledTasks, timeout = GlobalTimeout },
{ok, MasterPid} =
supervisor:start_child(tf_master_sup, [self(), MaxWorkers]),
{ok, BiddingResult} =
gen_server:call(MasterPid, {do_my_bidding, Bidding}, infinity),
BiddingResult.
-spec result(BiddingResult) -> Result
when BiddingResult :: tf_bidding_result(),
Result :: result().
result(BiddingResult) ->
#tf_bidding_result{
completed = CompletedList,
individual_timeouts = IndividualTimeouts,
global_timeouts = Timeouts } = BiddingResult,
#{ completed => maps:from_list(CompletedList),
individual_timeouts => IndividualTimeouts,
global_timeouts => Timeouts }.
-spec old_style_result(BiddingResult) -> OldStyleResult
when BiddingResult :: tf_bidding_result(),
OldStyleResult :: {Completed, IndividualTimeouts, Timeouts},
Completed :: [{TaskId, TaskResult}],
IndividualTimeouts :: [TaskId],
Timeouts :: [TaskId],
TaskId :: term(),
TaskResult :: term().
old_style_result(BiddingResult) ->
#tf_bidding_result{
completed = CompletedList,
individual_timeouts = IndividualTimeouts,
global_timeouts = Timeouts } = BiddingResult,
{CompletedList, IndividualTimeouts, Timeouts}.