2011-04-03 21:23:12 -05:00
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
%%% @doc
|
|
|
|
%%% simple parrallel map. Originally provided by Joe Armstrong
|
|
|
|
%%% on the erlang questions mailing list.
|
|
|
|
%%% @end
|
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
-module(ec_plists).
|
|
|
|
|
|
|
|
-export([map/2,
|
|
|
|
map/3,
|
|
|
|
ftmap/2,
|
|
|
|
ftmap/3,
|
|
|
|
filter/2,
|
|
|
|
filter/3]).
|
|
|
|
|
2012-09-04 20:27:19 -05:00
|
|
|
-export_type([thunk/0]).
|
|
|
|
|
|
|
|
%%=============================================================================
|
|
|
|
%% Types
|
|
|
|
%%=============================================================================
|
|
|
|
-type thunk() :: fun((any()) -> any()).
|
|
|
|
|
2011-04-03 21:23:12 -05:00
|
|
|
%%=============================================================================
|
|
|
|
%% Public API
|
|
|
|
%%=============================================================================
|
|
|
|
|
|
|
|
%% @doc Takes a function and produces a list of the result of the function
|
|
|
|
%% applied to each element of the argument list. A timeout is optional.
|
|
|
|
%% In the event of a timeout or an exception the entire map will fail
|
|
|
|
%% with an excption with class throw.
|
|
|
|
-spec map(fun(), [any()]) -> [any()].
|
|
|
|
map(Fun, List) ->
|
|
|
|
map(Fun, List, infinity).
|
|
|
|
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec map(thunk(), [any()], timeout() | infinity) -> [any()].
|
2011-04-03 21:23:12 -05:00
|
|
|
map(Fun, List, Timeout) ->
|
|
|
|
run_list_fun_in_parallel(map, Fun, List, Timeout).
|
|
|
|
|
|
|
|
%% @doc Takes a function and produces a list of the result of the function
|
|
|
|
%% applied to each element of the argument list. A timeout is optional.
|
|
|
|
%% This function differes from regular map in that it is fault tolerant.
|
|
|
|
%% If a timeout or an exception occurs while processing an element in
|
|
|
|
%% the input list the ftmap operation will continue to function. Timeouts
|
|
|
|
%% and exceptions will be reflected in the output of this function.
|
|
|
|
%% All application level results are wrapped in a tuple with the tag
|
|
|
|
%% 'value'. Exceptions will come through as they are and timeouts will
|
|
|
|
%% return as the atom timeout.
|
2011-04-22 09:36:29 -05:00
|
|
|
%% This is useful when the ftmap is being used for side effects.
|
2011-04-03 21:23:12 -05:00
|
|
|
%% <pre>
|
|
|
|
%% 2> ftmap(fun(N) -> factorial(N) end, [1, 2, 1000000, "not num"], 100)
|
|
|
|
%% [{value, 1}, {value, 2}, timeout, {badmatch, ...}]
|
|
|
|
%% </pre>
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec ftmap(thunk(), [any()]) -> [{value, any()} | any()].
|
2011-04-03 21:23:12 -05:00
|
|
|
ftmap(Fun, List) ->
|
|
|
|
ftmap(Fun, List, infinity).
|
|
|
|
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec ftmap(thunk(), [any()], timeout() | infinity) -> [{value, any()} | any()].
|
2011-04-03 21:23:12 -05:00
|
|
|
ftmap(Fun, List, Timeout) ->
|
|
|
|
run_list_fun_in_parallel(ftmap, Fun, List, Timeout).
|
|
|
|
|
|
|
|
%% @doc Returns a list of the elements in the supplied list which
|
|
|
|
%% the function Fun returns true. A timeout is optional. In the
|
|
|
|
%% event of a timeout the filter operation fails.
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec filter(thunk(), [any()]) -> [any()].
|
2011-04-03 21:23:12 -05:00
|
|
|
filter(Fun, List) ->
|
|
|
|
filter(Fun, List, infinity).
|
|
|
|
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec filter(thunk(), [any()], timeout() | infinity) -> [any()].
|
2011-04-03 21:23:12 -05:00
|
|
|
filter(Fun, List, Timeout) ->
|
|
|
|
run_list_fun_in_parallel(filter, Fun, List, Timeout).
|
|
|
|
|
|
|
|
%%=============================================================================
|
|
|
|
%% Internal API
|
|
|
|
%%=============================================================================
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec run_list_fun_in_parallel(atom(), thunk(), [any()], timeout() | infinity) -> [any()].
|
2011-04-03 21:23:12 -05:00
|
|
|
run_list_fun_in_parallel(ListFun, Fun, List, Timeout) ->
|
|
|
|
LocalPid = self(),
|
2011-04-22 09:36:29 -05:00
|
|
|
Pids =
|
2011-09-26 11:48:14 -05:00
|
|
|
lists:map(fun(E) ->
|
|
|
|
Pid =
|
|
|
|
proc_lib:spawn(fun() ->
|
|
|
|
wait(LocalPid, Fun,
|
|
|
|
E, Timeout)
|
|
|
|
end),
|
|
|
|
{Pid, E}
|
|
|
|
end, List),
|
2011-04-03 21:23:12 -05:00
|
|
|
gather(ListFun, Pids).
|
|
|
|
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec wait(pid(), thunk(), any(), timeout() | infinity) -> any().
|
2011-04-03 21:23:12 -05:00
|
|
|
wait(Parent, Fun, E, Timeout) ->
|
|
|
|
WaitPid = self(),
|
|
|
|
Child = spawn(fun() ->
|
|
|
|
do_f(WaitPid, Fun, E)
|
|
|
|
end),
|
|
|
|
|
|
|
|
wait(Parent, Child, Timeout).
|
|
|
|
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec wait(pid(), pid(), timeout() | infinity) -> any().
|
2011-04-03 21:23:12 -05:00
|
|
|
wait(Parent, Child, Timeout) ->
|
|
|
|
receive
|
|
|
|
{Child, Ret} ->
|
|
|
|
Parent ! {self(), Ret}
|
|
|
|
after Timeout ->
|
|
|
|
exit(Child, timeout),
|
|
|
|
Parent ! {self(), timeout}
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec gather(atom(), [any()]) -> [any()].
|
|
|
|
gather(map, PidElementList) ->
|
|
|
|
map_gather(PidElementList);
|
|
|
|
gather(ftmap, PidElementList) ->
|
|
|
|
ftmap_gather(PidElementList);
|
|
|
|
gather(filter, PidElementList) ->
|
|
|
|
filter_gather(PidElementList).
|
|
|
|
|
|
|
|
-spec map_gather([pid()]) -> [any()].
|
|
|
|
map_gather([{Pid, _E} | Rest]) ->
|
|
|
|
receive
|
|
|
|
{Pid, {value, Ret}} ->
|
2011-09-26 11:48:14 -05:00
|
|
|
[Ret|map_gather(Rest)];
|
2011-10-27 09:56:22 -05:00
|
|
|
%% timeouts fall here too. Should timeouts be a return value
|
|
|
|
%% or an exception? I lean toward return value, but the code
|
|
|
|
%% is easier with the exception. Thoughts?
|
2011-04-03 21:23:12 -05:00
|
|
|
{Pid, Exception} ->
|
2011-09-26 11:48:14 -05:00
|
|
|
killall(Rest),
|
|
|
|
throw(Exception)
|
2011-04-03 21:23:12 -05:00
|
|
|
end;
|
|
|
|
map_gather([]) ->
|
|
|
|
[].
|
|
|
|
|
|
|
|
-spec ftmap_gather([pid()]) -> [any()].
|
|
|
|
ftmap_gather([{Pid, _E} | Rest]) ->
|
|
|
|
receive
|
|
|
|
{Pid, Value} -> [Value|ftmap_gather(Rest)]
|
|
|
|
end;
|
|
|
|
ftmap_gather([]) ->
|
|
|
|
[].
|
|
|
|
|
|
|
|
-spec filter_gather([pid()]) -> [any()].
|
|
|
|
filter_gather([{Pid, E} | Rest]) ->
|
|
|
|
receive
|
|
|
|
{Pid, {value, false}} ->
|
2011-09-26 11:48:14 -05:00
|
|
|
filter_gather(Rest);
|
2011-04-03 21:23:12 -05:00
|
|
|
{Pid, {value, true}} ->
|
2011-09-26 11:48:14 -05:00
|
|
|
[E|filter_gather(Rest)];
|
2011-04-03 21:23:12 -05:00
|
|
|
{Pid, {value, NotBool}} ->
|
2011-09-26 11:48:14 -05:00
|
|
|
killall(Rest),
|
|
|
|
throw({bad_return_value, NotBool});
|
2011-04-03 21:23:12 -05:00
|
|
|
{Pid, Exception} ->
|
2011-09-26 11:48:14 -05:00
|
|
|
killall(Rest),
|
|
|
|
throw(Exception)
|
2011-04-03 21:23:12 -05:00
|
|
|
end;
|
|
|
|
filter_gather([]) ->
|
|
|
|
[].
|
|
|
|
|
2012-09-04 20:27:19 -05:00
|
|
|
-spec do_f(pid(), thunk(), any()) -> no_return().
|
2011-04-03 21:23:12 -05:00
|
|
|
do_f(Parent, F, E) ->
|
|
|
|
try
|
|
|
|
Result = F(E),
|
|
|
|
Parent ! {self(), {value, Result}}
|
|
|
|
catch
|
|
|
|
_Class:Exception ->
|
2011-10-27 09:56:22 -05:00
|
|
|
%% Losing class info here, but since throw does not accept
|
|
|
|
%% that arg anyhow and forces a class of throw it does not
|
|
|
|
%% matter.
|
2011-04-03 21:23:12 -05:00
|
|
|
Parent ! {self(), Exception}
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec killall([pid()]) -> ok.
|
|
|
|
killall([{Pid, _E}|T]) ->
|
|
|
|
exit(Pid, kill),
|
|
|
|
killall(T);
|
|
|
|
killall([]) ->
|
|
|
|
ok.
|
|
|
|
|
|
|
|
%%=============================================================================
|
|
|
|
%% Tests
|
|
|
|
%%=============================================================================
|
|
|
|
|
|
|
|
-ifndef(NOTEST).
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
|
|
|
|
map_good_test() ->
|
|
|
|
Results = map(fun(_) ->
|
|
|
|
ok
|
|
|
|
end,
|
|
|
|
lists:seq(1, 5), infinity),
|
|
|
|
?assertMatch([ok, ok, ok, ok, ok],
|
|
|
|
Results).
|
|
|
|
|
|
|
|
ftmap_good_test() ->
|
|
|
|
Results = ftmap(fun(_) ->
|
2011-09-26 11:48:14 -05:00
|
|
|
ok
|
|
|
|
end,
|
|
|
|
lists:seq(1, 3), infinity),
|
2011-04-03 21:23:12 -05:00
|
|
|
?assertMatch([{value, ok}, {value, ok}, {value, ok}],
|
|
|
|
Results).
|
|
|
|
|
|
|
|
filter_good_test() ->
|
|
|
|
Results = filter(fun(X) ->
|
|
|
|
X == show
|
|
|
|
end,
|
|
|
|
[show, show, remove], infinity),
|
|
|
|
?assertMatch([show, show],
|
|
|
|
Results).
|
|
|
|
|
|
|
|
map_timeout_test() ->
|
|
|
|
Results =
|
2011-09-26 11:48:14 -05:00
|
|
|
try
|
|
|
|
map(fun(T) ->
|
|
|
|
timer:sleep(T),
|
|
|
|
T
|
|
|
|
end,
|
|
|
|
[1, 100], 10)
|
|
|
|
catch
|
|
|
|
C:E -> {C, E}
|
|
|
|
end,
|
2011-04-03 21:23:12 -05:00
|
|
|
?assertMatch({throw, timeout}, Results).
|
|
|
|
|
|
|
|
ftmap_timeout_test() ->
|
|
|
|
Results = ftmap(fun(X) ->
|
2011-09-26 11:48:14 -05:00
|
|
|
timer:sleep(X),
|
|
|
|
true
|
|
|
|
end,
|
|
|
|
[100, 1], 10),
|
2011-04-03 21:23:12 -05:00
|
|
|
?assertMatch([timeout, {value, true}], Results).
|
|
|
|
|
|
|
|
filter_timeout_test() ->
|
|
|
|
Results =
|
2011-09-26 11:48:14 -05:00
|
|
|
try
|
|
|
|
filter(fun(T) ->
|
|
|
|
timer:sleep(T),
|
|
|
|
T == 1
|
|
|
|
end,
|
|
|
|
[1, 100], 10)
|
|
|
|
catch
|
|
|
|
C:E -> {C, E}
|
|
|
|
end,
|
2011-04-03 21:23:12 -05:00
|
|
|
?assertMatch({throw, timeout}, Results).
|
|
|
|
|
|
|
|
map_bad_test() ->
|
|
|
|
Results =
|
2011-09-26 11:48:14 -05:00
|
|
|
try
|
|
|
|
map(fun(_) ->
|
|
|
|
throw(test_exception)
|
|
|
|
end,
|
|
|
|
lists:seq(1, 5), infinity)
|
|
|
|
catch
|
|
|
|
C:E -> {C, E}
|
|
|
|
end,
|
2011-04-03 21:23:12 -05:00
|
|
|
?assertMatch({throw, test_exception}, Results).
|
|
|
|
|
|
|
|
ftmap_bad_test() ->
|
|
|
|
Results =
|
2011-09-26 11:48:14 -05:00
|
|
|
ftmap(fun(2) ->
|
|
|
|
throw(test_exception);
|
|
|
|
(N) ->
|
|
|
|
N
|
|
|
|
end,
|
|
|
|
lists:seq(1, 5), infinity),
|
2011-04-22 09:36:29 -05:00
|
|
|
?assertMatch([{value, 1}, test_exception, {value, 3},
|
2011-09-26 11:48:14 -05:00
|
|
|
{value, 4}, {value, 5}] , Results).
|
2011-04-03 21:23:12 -05:00
|
|
|
|
|
|
|
-endif.
|