├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin └── rebar3 ├── include └── wrek_event.hrl ├── rebar.config ├── rebar.lock ├── src ├── wrek.app.src ├── wrek.erl ├── wrek_app.erl ├── wrek_event.erl ├── wrek_exec.erl ├── wrek_sup.erl ├── wrek_t.erl ├── wrek_utils.erl ├── wrek_vert.erl └── wrek_vert_t.erl └── test ├── verts ├── wrek_bad_vert.erl ├── wrek_echo_vert.erl ├── wrek_get_sandbox_vert.erl ├── wrek_get_vert.erl ├── wrek_id_vert.erl ├── wrek_make_sandbox_vert.erl ├── wrek_reuse_sandbox_vert.erl ├── wrek_sleep_vert.erl └── wrek_true_vert.erl ├── wrek_cb_event_handler.erl ├── wrek_test_handler.erl └── wrek_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | logs 15 | _build 16 | .idea 17 | *.iml 18 | rebar3.crashdump 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | notifications: 3 | email: false 4 | otp_release: 5 | - 21.0.3 6 | - 20.3 7 | - 19.3 8 | script: "make travis" 9 | sudo: false 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Richard Kallos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ELVIS=./bin/elvis 2 | REBAR=./bin/rebar3 3 | 4 | all: compile 5 | 6 | clean: 7 | @echo "Running rebar3 clean..." 8 | @$(REBAR) clean -a 9 | 10 | compile: 11 | @echo "Running rebar3 compile..." 12 | @$(REBAR) as compile compile 13 | 14 | dialyzer: 15 | @echo "Running rebar3 dialyze..." 16 | @$(REBAR) dialyzer 17 | 18 | elvis: 19 | @echo "Running elvis rock..." 20 | @$(ELVIS) rock 21 | 22 | eunit: 23 | @echo "Running rebar3 eunit..." 24 | @$(REBAR) eunit 25 | 26 | relx: 27 | @echo "Running rebar3 as prod release" 28 | @$(REBAR) as prod release 29 | 30 | test: xref eunit dialyzer 31 | 32 | travis: compile test 33 | 34 | xref: 35 | @echo "Running rebar3 xref..." 36 | @$(REBAR) xref 37 | 38 | .PHONY: clean compile dialyzer elvis eunit test travis xref 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wrek 2 | ===== 3 | 4 | __Author:__ Richard Kallos 5 | 6 | Lightweight concurrent DAG execution engine 7 | 8 | 9 | Description 10 | ----------- 11 | 12 | Wrek is designed to execute task dependency graphs concurrently. A vertex `V` 13 | in a task dependency graph can execute once all vertices with paths to `V` have 14 | finished executing. This execution model is similar to tools like Concourse CI. 15 | 16 | To retrieve events from wrek, you can pass in the pid of a `gen_event` process, 17 | and add handlers as you see fit. 18 | 19 | 20 | Requirements 21 | ------------ 22 | 23 | * Erlang 19.0+ 24 | 25 | 26 | Build 27 | ----- 28 | 29 | $ rebar3 compile 30 | 31 | 32 | How to use 33 | ---------- 34 | 35 | 1. Write callback modules that implement the `wrek_vert` behaviour. 36 | 2. Create a map `Map` reflecting the structure of the graph you want to run. 37 | 3. `wrek:start(Map)` or `wrek:start(Map, Opts)`. 38 | 39 | 40 | Options 41 | ------- 42 | 43 | - `{event_manager, pid()}`: Specify a `gen_event` process to forward events to 44 | - `{failure_mode, partial | total}` (default: total): Switch between 45 | partial and total failure modes. Total failure will immediately shut 46 | down all running vertices within a DAG. Partial failure will cancel 47 | running all tasks reachable by any failed vertex, but will continue 48 | until all vertices finish running. 49 | - `{global_timeout, integer() | undefined}`: Specify a timeout in 50 | milliseconds by which the entire DAG will shutdown if it hasn't 51 | already finished. An `undefined` value means no timeout. (default: 52 | undefined) 53 | 54 | Example 55 | ------- 56 | ```erlang 57 | -module(true_vert). 58 | -behaviour(wrek_vert). 59 | 60 | -export([run/2]). 61 | 62 | run(_Args, Parent) -> 63 | {ok, Fun} = wrek_vert:exec(Parent, ".", "true"), 64 | ok = Fun(), 65 | {ok, #{}}. 66 | ``` 67 | 68 | ```erlang 69 | 1> Map = #{ 70 | one => #{module => true_vert, args => [], deps => []}, 71 | two => #{module => true_vert, args => [], deps => [one]}, 72 | three => #{module => true_vert, args => [], deps => [one]}}. 73 | 2> wrek:start(Map). % Runs one, then two+three concurrently 74 | ``` 75 | 76 | Individual Vertex Timeout 77 | ------------------------- 78 | 79 | ``` erlang 80 | VertDefn = #{module => wrek_sleep_vert, args => [], deps => [], timeout => 10}, 81 | 82 | ``` 83 | 84 | Where `timeout` is the number of milliseconds until normal completion 85 | or forced timeout. 86 | -------------------------------------------------------------------------------- /bin/rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkallos/wrek/3859e9efdf21227e6e8e0ea81095b229eceb6641/bin/rebar3 -------------------------------------------------------------------------------- /include/wrek_event.hrl: -------------------------------------------------------------------------------- 1 | -record(wrek_event, { 2 | timestamp = erlang:monotonic_time() :: integer(), 3 | id = undefined :: pos_integer() | {pos_integer(), pos_integer()}, 4 | type = undefined :: atom() | {atom(), atom()}, 5 | msg = undefined :: any() 6 | }). 7 | -type wrek_event() :: #wrek_event{}. 8 | -export_type([wrek_event/0]). 9 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {erlexec, "1.9.3"} 3 | ]}. 4 | 5 | {profiles, [ 6 | {compile, [ 7 | {erl_opts, [ 8 | debug_info, 9 | warnings_as_errors, 10 | warn_export_all, 11 | warn_export_vars, 12 | warn_missing_spec, 13 | warn_obsolete_guard, 14 | warn_shadow_vars, 15 | warn_untyped_record, 16 | warn_unused_import, 17 | warn_unused_vars, 18 | {platform_define, "linux", 'LINUX'} 19 | ]} 20 | ]} 21 | ]}. 22 | 23 | {xref_checks, [ 24 | deprecated_functions, 25 | deprecated_function_calls, 26 | locals_not_used, 27 | undefined_functions, 28 | undefined_function_calls 29 | ]}. 30 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.1.0", 2 | [{<<"erlexec">>,{pkg,<<"erlexec">>,<<"1.9.3">>},0}]}. 3 | [ 4 | {pkg_hash,[ 5 | {<<"erlexec">>, <<"3D72AC65424CED35B9658A50E5A0C9DBD5F383E28AC9096E557F0D62926DD8E4">>}]} 6 | ]. 7 | -------------------------------------------------------------------------------- /src/wrek.app.src: -------------------------------------------------------------------------------- 1 | {application, wrek, 2 | [{description, "An OTP application"}, 3 | {vsn, "3.1.1"}, 4 | {registered, [wrek_sup]}, 5 | {mod, {wrek_app, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib 9 | ]}, 10 | {env,[]}, 11 | {modules, []}, 12 | 13 | {maintainers, []}, 14 | {licenses, ["Apache 2.0"]}, 15 | {links, []} 16 | ]}. 17 | -------------------------------------------------------------------------------- /src/wrek.erl: -------------------------------------------------------------------------------- 1 | -module(wrek). 2 | 3 | -export([put_sandbox/2, 4 | start/1, 5 | start/2]). 6 | 7 | -behaviour(gen_server). 8 | -export([ 9 | code_change/3, 10 | handle_call/3, 11 | handle_cast/2, 12 | handle_info/2, 13 | init/1, 14 | terminate/2 15 | ]). 16 | 17 | -type dag_id() :: pos_integer(). 18 | -type vert_id() :: {pos_integer(), pos_integer()}. 19 | 20 | -define(id(), erlang:unique_integer([positive, monotonic])). 21 | 22 | -type vert_defn() :: #{ 23 | module := module(), 24 | args := list(), 25 | deps := list() 26 | }. 27 | 28 | -type dag_map() :: #{any() := vert_defn()} | [{any(), vert_defn()}]. 29 | 30 | -type option() :: {event_manager, pid()} | {failure_mode, partial | total} | 31 | {global_timeout, pos_integer() | undefined}. 32 | 33 | -export_type([ 34 | dag_id/0, 35 | dag_map/0, 36 | option/0, 37 | vert_defn/0, 38 | vert_id/0 39 | ]). 40 | 41 | -record(state, { 42 | event_mgr = undefined :: pid() | undefined, 43 | failure_mode = total :: partial | total, 44 | id = ?id() :: dag_id(), 45 | sandbox = undefined :: file:filename_all() | undefined, 46 | wrek = undefined :: wrek_t:t() | undefined 47 | }). 48 | 49 | -type state() :: #state{}. 50 | 51 | 52 | -spec put_sandbox(pid(), file:filename_all()) -> {ok, wrek_vert_t:t()}. 53 | 54 | put_sandbox(Pid, Dir) -> 55 | gen_server:call(Pid, {put_sandbox, Dir}). 56 | 57 | 58 | -spec start(dag_map()) -> supervisor:startchild_ret(). 59 | 60 | start(Defns) -> 61 | start(Defns, []). 62 | 63 | 64 | -spec start(dag_map(), [option()]) -> supervisor:startchild_ret(). 65 | 66 | start(Defns, Opts) -> 67 | Id = ?id(), 68 | ChildSpec = #{ 69 | id => Id, 70 | start => {gen_server, start_link, [?MODULE, {Id, Defns, Opts}, []]}, 71 | restart => temporary, 72 | type => worker 73 | }, 74 | supervisor:start_child(wrek_sup, ChildSpec). 75 | 76 | 77 | %% callbacks 78 | 79 | -spec code_change(_, _, state()) -> {ok, state()}. 80 | 81 | code_change(_Req, _From, State) -> 82 | {ok, State}. 83 | 84 | 85 | -spec handle_call(_, _, state()) -> {reply, _, state()} | {stop, _, state()}. 86 | 87 | handle_call({put_sandbox, Dir}, {From, _}, State = #state{wrek = Wrek}) -> 88 | {ok, {Name, Vert}} = wrek_t:child(Wrek, From), 89 | Vert2 = wrek_vert_t:set_dir(Vert, Dir), 90 | Wrek2 = wrek_t:add_vertex(Wrek, Name, Vert2), 91 | {reply, {ok, Vert2}, State#state{wrek = Wrek2}}; 92 | 93 | handle_call(sandbox, _From, State) -> 94 | {reply, State#state.sandbox, State}; 95 | 96 | handle_call(_Req, _From, State) -> 97 | {reply, ok, State}. 98 | 99 | 100 | -spec handle_cast(_, state()) -> {noreply, state()}. 101 | 102 | handle_cast(_Req, State) -> 103 | {noreply, State}. 104 | 105 | 106 | -spec handle_info(_, state()) -> {noreply, state()}. 107 | 108 | handle_info(timeout, #state{ 109 | event_mgr = EvMgr, 110 | id = Id 111 | } = State) -> 112 | wrek_event:wrek_error(EvMgr, Id, timeout), 113 | {stop, timeout, State}; 114 | 115 | handle_info({'EXIT', Pid, {shutdown, {ok, Data}}}, State) -> 116 | #state{wrek = Wrek} = State, 117 | 118 | Wrek2 = wrek_t:child_succeeded(Wrek, Pid, Data), 119 | start_verts_or_exit(State#state{wrek = Wrek2}); 120 | 121 | handle_info({'EXIT', Pid, {shutdown, Reason}}, State) -> 122 | #state{ 123 | event_mgr = EvMgr, 124 | failure_mode = FailMode, 125 | id = Id, 126 | wrek = Wrek 127 | } = State, 128 | {ok, {Name, _Vert}} = wrek_t:child(Wrek, Pid), 129 | wrek_event:wrek_error(EvMgr, Id, {vert, Name}), 130 | case FailMode of 131 | total -> 132 | {stop, {error, Reason}, State}; 133 | partial -> 134 | Wrek2 = wrek_t:child_failed(Wrek, Pid, Reason), 135 | State2 = State#state{wrek = Wrek2}, 136 | State3 = propagate_partial_failure(State2, Name), 137 | start_verts_or_exit(State3) 138 | end; 139 | 140 | handle_info(_Req, State) -> 141 | {noreply, State}. 142 | 143 | 144 | -spec init({dag_id(), dag_map(), [option()]}) -> {ok, state()} | {stop, _}. 145 | 146 | init({Id, DagMap, Opts}) -> 147 | process_flag(trap_exit, true), 148 | 149 | {ok, Wrek} = wrek_t:from_verts(DagMap), 150 | 151 | EvMgr = proplists:get_value(event_manager, Opts, undefined), 152 | FailMode = proplists:get_value(failure_mode, Opts, total), 153 | 154 | case proplists:get_value(global_timeout, Opts, undefined) of 155 | T when is_integer(T) -> 156 | {ok, _TRef} = timer:send_after(T, timeout); 157 | undefined -> ok 158 | end, 159 | 160 | Sandbox = make_dag_sandbox(Id), 161 | 162 | State = #state{ 163 | event_mgr = EvMgr, 164 | failure_mode = FailMode, 165 | id = Id, 166 | sandbox = Sandbox, 167 | wrek = Wrek 168 | }, 169 | 170 | wrek_event:wrek_start(EvMgr, Id, DagMap), 171 | 172 | {ok, State2} = start_verts(State), 173 | 174 | case wrek_t:is_active(State2#state.wrek) of 175 | false -> 176 | Reason = {unable_to_start, DagMap}, 177 | wrek_event:wrek_error(EvMgr, Id, Reason), 178 | {stop, Reason}; 179 | true -> 180 | {ok, State2} 181 | end. 182 | 183 | 184 | -spec terminate(_, state()) -> ok. 185 | 186 | terminate(_Reason, _State) -> 187 | ok. 188 | 189 | %% private 190 | 191 | -define(DIRNAME, 192 | lists:flatten( 193 | io_lib:format( 194 | "~B-~2..0B-~2..0B-~2..0B:~2..0B:~2..0B-~b", 195 | [Year, Month, Day, Hour, Min, Sec, Id] 196 | ))). 197 | 198 | -spec make_dag_sandbox(dag_id()) -> file:filename_all(). 199 | 200 | make_dag_sandbox(Id) -> 201 | BaseDir = application:get_env(wrek, sandbox_dir, "/tmp"), 202 | {{Year, Month, Day}, {Hour, Min, Sec}} = calendar:local_time(), 203 | wrek_utils:sandbox(BaseDir, ?DIRNAME). 204 | 205 | 206 | -spec make_vert_data(state(), _) -> any(). 207 | 208 | make_vert_data(#state{wrek = Wrek}, Name) -> 209 | Dependencies = 210 | [wrek_t:vertex(Wrek, N) || N <- wrek_t:dependencies(Wrek, Name)], 211 | maps:from_list([wrek_t:vertex(Wrek, Name) | Dependencies]). 212 | 213 | 214 | -spec propagate_partial_failure(state(), digraph:vertex()) -> state(). 215 | 216 | propagate_partial_failure(State, Name) -> 217 | #state{ 218 | event_mgr = EvMgr, 219 | id = Id, 220 | wrek = Wrek 221 | } = State, 222 | Wrek2 = lists:foldl(fun(VertName, Acc) -> 223 | wrek_event:wrek_msg(EvMgr, Id, {vert_cancelled, VertName}), 224 | wrek_t:cancel_vertex(Acc, VertName) 225 | end, Wrek, wrek_t:dependants(Wrek, Name)), 226 | State#state{wrek = Wrek2}. 227 | 228 | 229 | -spec start_verts(state()) -> {ok, state()} | {error, _}. 230 | 231 | start_verts(State = #state{wrek = Wrek}) -> 232 | ReadyVerts = wrek_t:ready_verts(Wrek), 233 | Wrek2 = lists:foldl(fun(Name, Acc) -> 234 | {ok, Pid} = start_vert(State, Name), 235 | wrek_t:child_started(Acc, Name, Pid) 236 | end, Wrek, ReadyVerts), 237 | State2 = State#state{wrek = Wrek2}, 238 | {ok, State2}. 239 | 240 | 241 | -spec start_vert(state(), digraph:vertex()) -> {ok, pid()}. 242 | 243 | start_vert(State, Name) -> 244 | #state{ 245 | event_mgr = EventMgr, 246 | id = DagId, 247 | wrek = Wrek 248 | } = State, 249 | VertId = {DagId, ?id()}, 250 | Wrek2 = wrek_t:set_vert_id(Wrek, Name, VertId), 251 | {Name, Vert} = wrek_t:vertex(Wrek2, Name), 252 | 253 | wrek_event:wrek_msg(EventMgr, DagId, {starting_vert, VertId}), 254 | 255 | Data = make_vert_data(State, Name), 256 | Args = {Vert, Data, EventMgr, self()}, 257 | gen_server:start_link(wrek_vert, Args, []). 258 | 259 | 260 | -spec start_verts_or_exit(state()) -> {noreply, state()} | {stop, normal, state()}. 261 | 262 | start_verts_or_exit(State = #state{wrek = Wrek}) -> 263 | case wrek_t:is_finished(Wrek) of 264 | true -> 265 | #state{ 266 | event_mgr = EvMgr, 267 | id = Id 268 | } = State, 269 | wrek_event:wrek_done(EvMgr, Id), 270 | {stop, normal, State}; 271 | false -> 272 | {ok, State2} = start_verts(State), 273 | {noreply, State2} 274 | end. 275 | -------------------------------------------------------------------------------- /src/wrek_app.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_app). 2 | 3 | -behaviour(application). 4 | -export([start/2, stop/1]). 5 | 6 | 7 | -spec start(application:start_type(), term()) -> 8 | {ok, pid()} | {ok, pid(), term()} | {error, term()}. 9 | 10 | start(_StartType, _StartArgs) -> 11 | wrek_sup:start_link(). 12 | 13 | 14 | -spec stop(term()) -> ok. 15 | 16 | stop(_State) -> 17 | ok. 18 | -------------------------------------------------------------------------------- /src/wrek_event.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_event). 2 | -include("wrek_event.hrl"). 3 | 4 | -export([exec_output/3, 5 | time_diff/2, 6 | time_diff/3, 7 | wrek_done/2, 8 | wrek_error/3, 9 | wrek_msg/3, 10 | wrek_start/3, 11 | vert_done/3, 12 | vert_start/5, 13 | vert_msg/3]). 14 | 15 | 16 | -type mgr() :: undefined | pid() | {atom(), atom()} | {via, atom(), atom()}. 17 | 18 | 19 | -spec time_diff(wrek_event(), wrek_event()) -> non_neg_integer(). 20 | 21 | time_diff(E1, E2) -> 22 | time_diff(E1, E2, microsecond). 23 | 24 | 25 | -spec time_diff(wrek_event(), wrek_event(), erlang:time_unit()) -> 26 | non_neg_integer(). 27 | 28 | time_diff(#wrek_event{timestamp = T1}, #wrek_event{timestamp = T2}, TimeUnit) -> 29 | erlang:convert_time_unit(T2 - T1, native, TimeUnit). 30 | 31 | 32 | -spec exec_output(mgr(), wrek:vert_id(), term()) -> ok. 33 | 34 | exec_output(undefined, _, _) -> ok; 35 | exec_output(Mgr, Id, Msg) -> 36 | gen_event:notify(Mgr, #wrek_event{id = Id, type = exec, msg = Msg}). 37 | 38 | 39 | -spec wrek_done(mgr(), wrek:dag_id()) -> ok. 40 | 41 | wrek_done(undefined, _) -> ok; 42 | wrek_done(Mgr, Id) -> 43 | gen_event:notify(Mgr, #wrek_event{id = Id, type = {wrek, done}}). 44 | 45 | 46 | -spec wrek_error(mgr(), wrek:dag_id(), term()) -> ok. 47 | 48 | wrek_error(undefined, _, _) -> ok; 49 | wrek_error(Mgr, Id, Msg) -> 50 | gen_event:notify(Mgr, #wrek_event{id = Id, type = {wrek, error}, msg = Msg}). 51 | 52 | 53 | -spec wrek_msg(mgr(), wrek:dag_id(), term()) -> ok. 54 | 55 | wrek_msg(undefined, _, _) -> ok; 56 | wrek_msg(Mgr, Id, Msg) -> 57 | gen_event:notify(Mgr, #wrek_event{id = Id, type = {wrek, msg}, msg = Msg}). 58 | 59 | 60 | -spec wrek_start(mgr(), wrek:dag_id(), term()) -> ok. 61 | 62 | wrek_start(undefined, _, _) -> ok; 63 | wrek_start(Mgr, Id, Map) -> 64 | gen_event:notify(Mgr, #wrek_event{id = Id, type = {wrek, start}, msg = Map}). 65 | 66 | 67 | -spec vert_done(mgr(), wrek:vert_id(), term()) -> ok. 68 | 69 | vert_done(undefined, _, _) -> ok; 70 | vert_done(Mgr, Id, Res) -> 71 | gen_event:notify(Mgr, #wrek_event{id = Id, type = {vert, done}, msg = Res}). 72 | 73 | 74 | -spec vert_start(mgr(), wrek:vert_id(), term(), module(), list()) -> ok. 75 | 76 | vert_start(undefined, _, _, _, _) -> ok; 77 | vert_start(Mgr, Id, Name, Module, Args) -> 78 | gen_event:notify(Mgr, #wrek_event{id = Id, type = {vert, start}, msg = {Name, Module, Args}}). 79 | 80 | 81 | -spec vert_msg(mgr(), wrek:vert_id(), term()) -> ok. 82 | 83 | vert_msg(undefined, _, _) -> ok; 84 | vert_msg(Mgr, Id, Msg) -> 85 | gen_event:notify(Mgr, #wrek_event{id = Id, type = {vert, msg}, msg = Msg}). 86 | -------------------------------------------------------------------------------- /src/wrek_exec.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_exec). 2 | 3 | -export([exec/4]). 4 | 5 | -type env() :: [{string(), string()}]. 6 | 7 | 8 | -spec exec(file:filename_all(), string(), env(), fun()) -> 9 | {ok, pid(), integer()} | {error, _}. 10 | 11 | exec(Dir0, Cmd0, Env, EventFun) -> 12 | Fun = fun(Fd, _OsPid, Data) -> 13 | EventFun({Fd, Data}) 14 | end, 15 | 16 | Dir = case Dir0 of 17 | D when is_binary(D) -> 18 | binary_to_list(D); 19 | D when is_list(D) -> 20 | D 21 | end, 22 | 23 | ExecOpts = [ 24 | {cd, Dir}, 25 | {env, Env}, 26 | {kill_timeout, 0}, 27 | {stdout, Fun}, 28 | {stderr, Fun} 29 | ], 30 | 31 | Cmd = lists:flatten(Cmd0), 32 | 33 | case exec:run_link(Cmd, ExecOpts) of 34 | {ok, _Pid, _OsPid} = Ok -> Ok; 35 | {error, _Reason} = Err -> Err 36 | end. 37 | -------------------------------------------------------------------------------- /src/wrek_sup.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_sup). 2 | 3 | -export([start_link/0]). 4 | 5 | -behaviour(supervisor). 6 | -export([init/1]). 7 | 8 | -define(SERVER, ?MODULE). 9 | 10 | 11 | -spec start_link() -> {ok, pid()} | ignore | {error, term()}. 12 | 13 | start_link() -> 14 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 15 | 16 | 17 | %% Callbacks 18 | 19 | -spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. 20 | 21 | init([]) -> 22 | SupFlags = #{ 23 | strategy => one_for_one, 24 | intensity => 0, 25 | period => 1 26 | }, 27 | {ok, {SupFlags, []}}. 28 | -------------------------------------------------------------------------------- /src/wrek_t.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_t). 2 | 3 | -export([ 4 | add_vertex/3, 5 | cancel_vertex/2, 6 | child/2, 7 | child_failed/3, 8 | child_started/3, 9 | child_succeeded/3, 10 | dependants/2, 11 | dependencies/2, 12 | edge/2, 13 | edges/1, 14 | is_active/1, 15 | is_finished/1, 16 | format/1, 17 | from_verts/1, 18 | ready_verts/1, 19 | remove_child/2, 20 | set_vert_id/3, 21 | vertex/2, 22 | vertices/1 23 | ]). 24 | 25 | -type dag_t() :: digraph:graph(). 26 | -type defn_t() :: wrek:vert_defn(). 27 | -type edge_t() :: digraph:edge(). 28 | -type label_t() :: digraph:label(). 29 | -type name_t() :: any(). 30 | -type vert_t() :: digraph:vertex(). 31 | 32 | -define(T, ?MODULE). 33 | 34 | -record(?T, { 35 | children = #{} :: #{pid() => vert_t()}, 36 | dag = new() :: dag_t() 37 | }). 38 | 39 | -type t() :: #?T{}. 40 | 41 | -export_type([ 42 | t/0 43 | ]). 44 | 45 | 46 | -spec add_vertex(t(), vert_t(), wrek_vert_t:t()) -> t(). 47 | 48 | add_vertex(T = #?T{dag = Dag}, Name, Vert) -> 49 | digraph:add_vertex(Dag, Name, Vert), 50 | T. 51 | 52 | 53 | -spec cancel_vertex(t(), vert_t()) -> t(). 54 | 55 | cancel_vertex(T = #?T{}, Name) -> 56 | {Name, Vert} = vertex(T, Name), 57 | Vert2 = wrek_vert_t:cancel(Vert), 58 | add_vertex(T, Name, Vert2). 59 | 60 | 61 | -spec child(t(), pid()) -> {ok, {vert_t(), wrek_vert_t:t()}} | false. 62 | 63 | child(T = #?T{children = Children}, Pid) -> 64 | case Children of 65 | #{Pid := Name} -> 66 | case vertex(T, Name) of 67 | false -> 68 | false; 69 | Vert -> 70 | {ok, Vert} 71 | end; 72 | _ -> 73 | false 74 | end. 75 | 76 | 77 | -spec child_failed(t(), pid(), any()) -> t(). 78 | 79 | child_failed(T = #?T{}, Pid, Reason) -> 80 | {ok, {Name, Vert}} = child(T, Pid), 81 | Vert2 = wrek_vert_t:fail(Vert, Reason), 82 | T2 = add_vertex(T, Name, Vert2), 83 | T3 = remove_child(T2, Pid), 84 | T3. 85 | 86 | 87 | -spec child_started(t(), vert_t(), pid()) -> t(). 88 | 89 | child_started(T = #?T{children = Children}, Name, Pid) -> 90 | Children2 = Children#{Pid => Name}, 91 | T#?T{children = Children2}. 92 | 93 | 94 | -spec child_succeeded(t(), pid(), map()) -> t(). 95 | 96 | child_succeeded(T = #?T{}, Pid, Result) -> 97 | {ok, {Name, Vert}} = child(T, Pid), 98 | Vert2 = wrek_vert_t:succeed(Vert, Result), 99 | T2 = add_vertex(T, Name, Vert2), 100 | T3 = remove_child(T2, Pid), 101 | T3. 102 | 103 | 104 | -spec dependants(t(), vert_t()) -> [vert_t()]. 105 | 106 | dependants(#?T{dag = Dag}, Name) -> 107 | digraph_utils:reachable_neighbours([Name], Dag). 108 | 109 | 110 | -spec dependencies(t(), vert_t()) -> [vert_t()]. 111 | 112 | dependencies(#?T{dag = Dag}, Name) -> 113 | digraph_utils:reaching_neighbours([Name], Dag). 114 | 115 | 116 | -spec edge(t(), vert_t()) -> {edge_t(), vert_t(), vert_t(), label_t()} | false. 117 | 118 | edge(#?T{dag = Dag}, Edge) -> 119 | digraph:edge(Dag, Edge). 120 | 121 | 122 | -spec edges(t()) -> [edge_t()]. 123 | 124 | edges(#?T{dag = Dag}) -> 125 | digraph:edges(Dag). 126 | 127 | 128 | -spec format(t()) -> string(). 129 | 130 | format(T = #?T{}) -> 131 | Verts = [vertex(T, V) || V <- vertices(T)], 132 | Edges = [edge(T, E) || E <- edges(T)], 133 | io_lib:format("~p~n", [{Verts, Edges}]). 134 | 135 | 136 | -spec from_verts(map() | [{name_t(), wrek:vert_map()}]) -> 137 | {ok, t()} | {error, any()}. 138 | 139 | from_verts(Verts) when is_map(Verts) -> 140 | from_verts(maps:to_list(Verts)); 141 | 142 | from_verts(Proplist) -> 143 | case make_vertices(Proplist) of 144 | {ok, Verts} -> 145 | T = #?T{}, 146 | ok = add_vertices(T, Verts), 147 | case add_dependencies(T, Verts) of 148 | ok -> 149 | {ok, T}; 150 | {error, _} = Err -> 151 | Err 152 | end; 153 | {error, _} = Err -> 154 | Err 155 | end. 156 | 157 | 158 | -spec is_active(t()) -> boolean(). 159 | 160 | is_active(#?T{children = Children}) -> 161 | maps:size(Children) > 0. 162 | 163 | 164 | -spec is_finished(t()) -> boolean(). 165 | 166 | is_finished(T) -> 167 | lists:all(fun(VertName) -> 168 | {VertName, Vert} = vertex(T, VertName), 169 | wrek_vert_t:is_finished(Vert) 170 | end, vertices(T)). 171 | 172 | 173 | -spec ready_verts(t()) -> [vert_t()]. 174 | 175 | ready_verts(T = #?T{dag = Dag, children = Children}) -> 176 | lists:filter(fun(Name) -> 177 | {Name, Vert} = vertex(T, Name), 178 | is_vert_ready(T, Name) andalso 179 | not wrek_vert_t:is_finished(Vert) andalso 180 | not lists:member(Name, maps:values(Children)) 181 | end, digraph:vertices(Dag)). 182 | 183 | 184 | -spec remove_child(t(), pid()) -> t(). 185 | 186 | remove_child(T = #?T{children = Children}, Pid) -> 187 | T#?T{children = maps:remove(Pid, Children)}. 188 | 189 | 190 | -spec set_vert_id(t(), vert_t(), wrek:vert_id()) -> t(). 191 | 192 | set_vert_id(T = #?T{}, Name, Id) -> 193 | {Name, Vert} = vertex(T, Name), 194 | Vert2 = wrek_vert_t:set_id(Vert, Id), 195 | add_vertex(T, Name, Vert2), 196 | T. 197 | 198 | 199 | -spec vertex(t(), vert_t()) -> {vert_t(), label_t()} | false. 200 | 201 | vertex(#?T{dag = Dag}, Vert) -> 202 | digraph:vertex(Dag, Vert). 203 | 204 | 205 | -spec vertices(t()) -> [vert_t()]. 206 | 207 | vertices(#?T{dag = Dag}) -> 208 | digraph:vertices(Dag). 209 | 210 | 211 | %% private 212 | 213 | -spec add_dependencies(t(), [wrek_vert_t:t()]) -> 214 | ok | {error, any()}. 215 | 216 | add_dependencies(_T = #?T{}, []) -> 217 | ok; 218 | 219 | add_dependencies(T = #?T{}, [Vert | Rest]) -> 220 | Name = wrek_vert_t:name(Vert), 221 | Deps = wrek_vert_t:deps(Vert), 222 | case add_edges(T, Name, Deps) of 223 | ok -> 224 | add_dependencies(T, Rest); 225 | {error, _} = Err -> 226 | Err 227 | end. 228 | 229 | 230 | -spec add_edges(t(), name_t(), [name_t()]) -> 231 | ok | {error, any()}. 232 | 233 | add_edges(_T, _To, []) -> 234 | ok; 235 | 236 | add_edges(T = #?T{dag = Dag}, To, [From | Rest]) -> 237 | case digraph:add_edge(Dag, From, To) of 238 | {error, _} = Err -> 239 | Err; 240 | _ -> 241 | add_edges(T, To, Rest) 242 | end. 243 | 244 | 245 | -spec add_vertices(t(), [wrek_vert_t:t()]) -> ok. 246 | 247 | add_vertices(#?T{dag = Dag}, Defns) -> 248 | lists:foreach(fun(Vert) -> 249 | Name = wrek_vert_t:name(Vert), 250 | digraph:add_vertex(Dag, Name, Vert) 251 | end, Defns). 252 | 253 | 254 | -spec is_vert_ready(t(), vert_t()) -> boolean(). 255 | 256 | is_vert_ready(T, Name) -> 257 | case vertex(T, Name) of 258 | false -> 259 | false; 260 | {Name, Vert} -> 261 | lists:all(fun(Dep) -> 262 | {_, DepVert} = vertex(T, Dep), 263 | wrek_vert_t:has_succeeded(DepVert) 264 | end, wrek_vert_t:deps(Vert)) 265 | end. 266 | 267 | 268 | -spec make_vertices([{name_t(), defn_t()}]) -> 269 | {ok, [wrek_vert_t:t()]} | {error, any()}. 270 | 271 | make_vertices(Verts) -> 272 | make_vertices2(Verts, []). 273 | 274 | 275 | -spec make_vertices2([{name_t(), defn_t()}], [wrek_vert_t:t()]) -> 276 | {ok, [wrek_vert_t:t()]} | {error, any()}. 277 | 278 | make_vertices2([], Acc) -> 279 | {ok, Acc}; 280 | 281 | make_vertices2([{Name, Defn} | Rest], Acc) -> 282 | case wrek_vert_t:from_defn(Defn) of 283 | {ok, Vert} -> 284 | Vert2 = wrek_vert_t:set_name(Vert, Name), 285 | make_vertices2(Rest, [Vert2 | Acc]); 286 | {error, _} = Err -> 287 | Err 288 | end. 289 | 290 | 291 | -spec new() -> dag_t(). 292 | 293 | new() -> 294 | digraph:new([acyclic, protected]). 295 | -------------------------------------------------------------------------------- /src/wrek_utils.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_utils). 2 | 3 | -export([ 4 | rm/1, 5 | rmdir/1, 6 | sandbox/2 7 | ]). 8 | 9 | 10 | -spec rm(file:filename_all()) -> ok | {error, atom()}. 11 | 12 | rm(Path) -> 13 | case filelib:is_dir(Path) of 14 | true -> rmdir(Path); 15 | false -> 16 | case filelib:is_file(Path) of 17 | true -> file:delete(Path); 18 | false -> ok 19 | end 20 | end. 21 | 22 | 23 | -spec rmdir(file:filename_all()) -> ok | {error, atom()}. 24 | 25 | rmdir(Dir) -> 26 | case file:list_dir(Dir) of 27 | {error, enoent} -> ok; 28 | {ok, Files} -> 29 | lists:foreach(fun(F) -> rm(filename:join(Dir, F)) end, Files), 30 | ok = file:del_dir(Dir) 31 | end. 32 | 33 | 34 | -spec sandbox(file:filename_all(), string()) -> file:filename_all(). 35 | 36 | sandbox(BaseDir, Name) -> 37 | Dir = filename:join([BaseDir, Name]), 38 | ok = rm(Dir), 39 | ok = filelib:ensure_dir(Dir), 40 | ok = file:make_dir(Dir), 41 | Dir. 42 | -------------------------------------------------------------------------------- /src/wrek_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_vert). 2 | 3 | -export([ 4 | exec/3, 5 | exec/4, 6 | exec/5, 7 | get/3, 8 | get/4, 9 | get_all/1, 10 | get_sandbox/2, 11 | make_sandbox/1, 12 | reuse_sandbox/2, 13 | notify/2 14 | ]). 15 | 16 | -behaviour(gen_server). 17 | -export([ 18 | code_change/3, 19 | handle_call/3, 20 | handle_cast/2, 21 | handle_info/2, 22 | init/1, 23 | terminate/2 24 | ]). 25 | 26 | -record(state, { 27 | child = undefined :: pid() | undefined, 28 | data = undefined :: #{any() => wrek_vert_t:t()} | undefined, 29 | event_mgr = undefined :: pid() | undefined, 30 | parent = undefined :: pid() | undefined, 31 | pids = #{} :: #{pid() => [pid()] | {result, any()}}, 32 | vert = undefined :: wrek_vert_t:t() | undefined 33 | }). 34 | -type state() :: #state{}. 35 | 36 | 37 | -callback run(Args :: list(), Parent :: pid()) -> 38 | {ok, Result :: any()} | {error, Reason :: any()}. 39 | 40 | 41 | -spec exec(pid(), file:filename_all(), string()) -> 42 | {ok, fun()} | {error, any()}. 43 | 44 | exec(Pid, Dir0, Cmd0) -> 45 | gen_server:call(Pid, {exec, Dir0, Cmd0, []}). 46 | 47 | 48 | -spec exec(pid(), file:filename_all(), string(), list()) -> 49 | {ok, fun()} | {error, any()}. 50 | 51 | exec(Pid, Dir0, Cmd0, Env) -> 52 | gen_server:call(Pid, {exec, Dir0, Cmd0, Env}). 53 | 54 | 55 | -spec exec(pid(), file:filename_all(), string(), list(), fun((any()) -> any())) -> 56 | {ok, fun()} | {error, any()}. 57 | 58 | exec(Pid, Dir0, Cmd0, Env, EventFun) -> 59 | gen_server:call(Pid, {exec, Dir0, Cmd0, Env, EventFun}). 60 | 61 | 62 | -spec get(pid(), any(), any()) -> any(). 63 | 64 | get(Pid, Who, Key) -> 65 | get(Pid, Who, Key, undefined). 66 | 67 | 68 | -spec get(pid(), any(), any(), any()) -> any(). 69 | 70 | get(Pid, Who, Key, Default) -> 71 | gen_server:call(Pid, {get, Who, Key, Default}). 72 | 73 | 74 | -spec get_all(pid()) -> map(). 75 | 76 | get_all(Pid) -> 77 | gen_server:call(Pid, get_all). 78 | 79 | 80 | -spec get_sandbox(pid(), any()) -> file:filename_all() | undefined. 81 | 82 | get_sandbox(Pid, Who) -> 83 | gen_server:call(Pid, {get_sandbox, Who}). 84 | 85 | 86 | -spec make_sandbox(pid()) -> file:filename_all(). 87 | 88 | make_sandbox(Pid) -> 89 | gen_server:call(Pid, make_sandbox). 90 | 91 | 92 | -spec reuse_sandbox(pid(), any()) -> {ok, file:filename_all()} | {error, term()}. 93 | 94 | reuse_sandbox(Pid, Who) -> 95 | gen_server:call(Pid, {reuse_sandbox, Who}). 96 | 97 | 98 | -spec notify(pid(), any()) -> ok. 99 | 100 | notify(Pid, Msg) -> 101 | gen_server:cast(Pid, {notify, Msg}). 102 | 103 | 104 | %% callbacks 105 | 106 | -spec code_change(_, _, state()) -> {ok, state()}. 107 | 108 | code_change(_Req, _From, State) -> 109 | {ok, State}. 110 | 111 | 112 | -spec handle_call(_, _, state()) -> {reply, fun(), state()}. 113 | 114 | handle_call({exec, Dir, Cmd, Env}, _From, State = #state{event_mgr = EvMgr}) -> 115 | EventFun = 116 | fun(Msg) -> 117 | wrek_event:exec_output(EvMgr, id(State), Msg) 118 | end, 119 | handle_exec(Dir, Cmd, Env, EventFun, State); 120 | 121 | handle_call({exec, Dir, Cmd, Env, EventFun0}, _From, State = #state{event_mgr = EvMgr}) -> 122 | EventFun = 123 | fun(Msg) -> 124 | wrek_event:exec_output(EvMgr, id(State), Msg), 125 | EventFun0(Msg) 126 | end, 127 | handle_exec(Dir, Cmd, Env, EventFun, State); 128 | 129 | handle_call({get, Who0, Key, Default}, _From, State) -> 130 | Who = case Who0 of 131 | me -> name(State); 132 | Other -> Other 133 | end, 134 | 135 | case State#state.data of 136 | #{Who := Vert} -> 137 | case wrek_vert_t:kv(Vert) of 138 | #{Key := Val} -> 139 | {reply, Val, State}; 140 | _ -> 141 | {reply, Default, State} 142 | end; 143 | _ -> 144 | {reply, Default, State} 145 | end; 146 | 147 | handle_call(get_all, _From, State = #state{data = Data0}) -> 148 | Data = maps:fold(fun(Name, Vert, Acc) -> 149 | D = wrek_vert_t:kv(Vert), 150 | Acc#{Name => D} 151 | end, #{}, Data0), 152 | {reply, Data, State}; 153 | 154 | handle_call({get_sandbox, Who0}, _From, State) -> 155 | #state{data = Data} = State, 156 | Who = case Who0 of 157 | me -> name(State); 158 | Other -> Other 159 | end, 160 | 161 | case Data of 162 | #{Who := Vert} -> 163 | {reply, wrek_vert_t:dir(Vert), State}; 164 | _ -> 165 | {reply, undefined, State} 166 | end; 167 | 168 | handle_call(make_sandbox, _From, State = #state{data = Data, parent = Parent}) -> 169 | {_DagId, VertId} = id(State), 170 | DagDir = dag_dir(Parent), 171 | VertStr = integer_to_list(VertId), 172 | Dir = wrek_utils:sandbox(DagDir, VertStr), 173 | {ok, Vert2} = wrek:put_sandbox(Parent, Dir), 174 | Data2 = Data#{name(State) => Vert2}, 175 | {reply, Dir, State#state{data = Data2, vert = Vert2}}; 176 | 177 | handle_call({reuse_sandbox, Who}, _From, State = #state{parent = Parent}) -> 178 | #state{data = Data} = State, 179 | {Reply, State2} = case Data of 180 | #{Who := V} -> 181 | case wrek_vert_t:dir(V) of 182 | undefined -> 183 | {{error, {no_sandbox, Who}}, State}; 184 | Dir -> 185 | {ok, Vert2} = wrek:put_sandbox(Parent, Dir), 186 | Data2 = Data#{name(State) => Vert2}, 187 | {{ok, Dir}, State#state{data = Data2, vert = Vert2}} 188 | end; 189 | _ -> 190 | {{error, {no_vert, Who, Data}}, State} 191 | end, 192 | {reply, Reply, State2}; 193 | 194 | handle_call({waitpid, Pid}, From, State) -> 195 | State2 = add_waitpid(From, Pid, State), 196 | {noreply, State2}; 197 | 198 | handle_call(_Req, _From, State) -> 199 | {reply, ok, State}. 200 | 201 | 202 | -spec handle_cast(_, state()) -> {noreply, state()}. 203 | 204 | handle_cast({notify, Msg}, State = #state{event_mgr = EvMgr}) -> 205 | wrek_event:vert_msg(EvMgr, id(State), Msg), 206 | {noreply, State}; 207 | 208 | handle_cast(_Req, State) -> 209 | {noreply, State}. 210 | 211 | 212 | -spec handle_info(_, state()) -> 213 | {noreply, state()} | {stop, normal | {error, _}, state()}. 214 | 215 | handle_info(timeout, State = #state{event_mgr = EvMgr}) -> 216 | wrek_event:vert_done(EvMgr, id(State), timeout), 217 | {stop, {shutdown, timeout}, State}; 218 | 219 | handle_info({'EXIT', Pid, Term} = ExitMsg, State) -> 220 | #state{ 221 | child = Child, 222 | event_mgr = EvMgr, 223 | pids = Pids 224 | } = State, 225 | case Pid of 226 | Child -> 227 | wrek_event:vert_done(EvMgr, id(State), Term), 228 | {stop, {shutdown, Term}, State}; 229 | _ -> 230 | case Pids of 231 | #{Pid := Waits} -> 232 | Result = case Term of 233 | normal -> ok; 234 | {exit_status, _Status} = Exit -> 235 | {error, Exit}; 236 | _ -> Term 237 | end, 238 | lists:foreach(fun(From) -> 239 | gen_server:reply(From, Result) 240 | end, Waits), 241 | Pids2 = Pids#{Pid => {result, Result}}, 242 | State2 = State#state{pids = Pids2}, 243 | {noreply, State2}; 244 | _ -> 245 | Msg = io_lib:format("unexpected msg: ~p", [ExitMsg]), 246 | wrek_event:vert_msg(EvMgr, id(State), Msg), 247 | {noreply, State} 248 | end 249 | end; 250 | 251 | handle_info(_Req, State) -> 252 | {noreply, State}. 253 | 254 | 255 | -spec init({wrek_vert_t:t(), #{any() => wrek_vert_t:t()}, pid(), pid()}) -> 256 | {ok, state()}. 257 | 258 | init({Vert, Data, EventMgr, Parent}) -> 259 | process_flag(trap_exit, true), 260 | 261 | Id = wrek_vert_t:id(Vert), 262 | Name = wrek_vert_t:name(Vert), 263 | Module = wrek_vert_t:module(Vert), 264 | Args = wrek_vert_t:args(Vert), 265 | 266 | case wrek_vert_t:timeout(Vert) of 267 | Ms when is_integer(Ms) -> 268 | {ok, _TRef} = timer:send_after(Ms, timeout); 269 | _ -> ok 270 | end, 271 | 272 | wrek_event:vert_start(EventMgr, Id, Name, Module, Args), 273 | 274 | Self = self(), 275 | Pid = spawn_link(fun() -> 276 | Result = Module:run(Args, Self), 277 | exit(Result) 278 | end), 279 | 280 | State = #state{ 281 | child = Pid, 282 | data = Data, 283 | event_mgr = EventMgr, 284 | parent = Parent, 285 | vert = Vert 286 | }, 287 | {ok, State}. 288 | 289 | 290 | -spec terminate(_, state()) -> ok. 291 | 292 | terminate(_Reason, _State) -> 293 | ok. 294 | 295 | 296 | %% private 297 | 298 | -spec add_waitpid({pid(), term()}, pid(), state()) -> state(). 299 | 300 | add_waitpid(From, Pid, State = #state{pids = Pids}) -> 301 | Pids2 = case Pids of 302 | #{Pid := {result, Result}} -> 303 | gen_server:reply(From, Result), 304 | Pids; 305 | #{Pid := Waits} -> 306 | Pids#{Pid => Waits ++ [From]} 307 | end, 308 | State#state{pids = Pids2}. 309 | 310 | 311 | -spec dag_dir(pid()) -> file:filename_all(). 312 | 313 | dag_dir(Pid) -> 314 | gen_server:call(Pid, sandbox). 315 | 316 | 317 | -spec handle_exec(Dir, string(), Env, fun(), state()) -> 318 | {reply, {ok, fun()} | {error, _}, state()} 319 | when Dir :: file:filename_all(), Env :: [{string(), string()}]. 320 | 321 | handle_exec(Dir, Cmd, Env, EventFun, State = #state{pids = Pids}) -> 322 | case wrek_exec:exec(Dir, Cmd, Env, EventFun) of 323 | {ok, Pid, _OsPid} -> 324 | Self = self(), 325 | Fun = fun() -> gen_server:call(Self, {waitpid, Pid}, infinity) end, 326 | State2 = State#state{pids = Pids#{Pid => []}}, 327 | {reply, {ok, Fun}, State2}; 328 | Err -> 329 | {reply, Err, State} 330 | end. 331 | 332 | 333 | -spec id(state()) -> wrek:vert_id(). 334 | 335 | id(#state{vert = Vert}) -> 336 | wrek_vert_t:id(Vert). 337 | 338 | 339 | -spec name(state()) -> any(). 340 | 341 | name(#state{vert = Vert}) -> 342 | wrek_vert_t:name(Vert). 343 | -------------------------------------------------------------------------------- /src/wrek_vert_t.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_vert_t). 2 | 3 | -export([ 4 | new/0, 5 | cancel/1, 6 | fail/2, 7 | from_defn/1, 8 | has_succeeded/1, 9 | is_finished/1, 10 | succeed/2, 11 | to_list/1, 12 | % getters 13 | args/1, 14 | deps/1, 15 | dir/1, 16 | id/1, 17 | kv/1, 18 | module/1, 19 | name/1, 20 | reason/1, 21 | status/1, 22 | timeout/1, 23 | % setters 24 | set_args/2, 25 | set_deps/2, 26 | set_dir/2, 27 | set_id/2, 28 | set_kv/2, 29 | set_module/2, 30 | set_name/2, 31 | set_reason/2, 32 | set_status/2, 33 | set_timeout/2 34 | ]). 35 | 36 | -type args_t() :: list(). 37 | -type deps_t() :: list(). 38 | -type dir_t() :: file:filename_all() | undefined. 39 | -type id_t() :: wrek:vert_id() | undefined. 40 | -type kv_t() :: map(). 41 | -type module_t() :: module() | undefined. 42 | -type name_t() :: any(). 43 | -type reason_t() :: any(). 44 | -type status_t() :: failed | done | cancelled | undefined. 45 | -type timeout_t() :: pos_integer() | undefined. 46 | 47 | -define(T, ?MODULE). 48 | 49 | -record(?T, { 50 | args = [] :: args_t(), 51 | deps = [] :: deps_t(), 52 | dir = undefined :: dir_t(), 53 | id = undefined :: id_t(), 54 | kv = #{} :: kv_t(), 55 | module = undefined :: module_t(), 56 | name = undefined :: name_t(), 57 | reason = undefined :: reason_t(), 58 | status = undefined :: status_t(), 59 | timeout = undefined :: timeout_t() 60 | }). 61 | 62 | -type t() :: #?T{}. 63 | 64 | -export_type([ 65 | t/0 66 | ]). 67 | 68 | 69 | -spec new() -> t(). 70 | 71 | new() -> 72 | #?T{}. 73 | 74 | 75 | -spec cancel(t()) -> t(). 76 | 77 | cancel(Vert = #?T{}) -> 78 | set_status(Vert, cancelled). 79 | 80 | 81 | -spec fail(t(), reason_t()) -> t(). 82 | 83 | fail(Vert = #?T{}, Reason) -> 84 | Vert2 = set_reason(Vert, Reason), 85 | set_status(Vert2, failed). 86 | 87 | 88 | -spec from_defn(map() | t()) -> {ok, t()} | {error, any()}. 89 | 90 | from_defn(Map0) when is_map(Map0) -> 91 | Res0 = #?T{}, 92 | 93 | MandatoryFields = [ 94 | {module, fun set_module/2}, 95 | {args, fun set_args/2}, 96 | {deps, fun set_deps/2} 97 | ], 98 | 99 | OptionalFields = [ 100 | {name, fun set_name/2}, 101 | {timeout, fun set_timeout/2} 102 | ], 103 | 104 | case load_mandatory(Res0, Map0, MandatoryFields) of 105 | {error, _} = Err -> 106 | Err; 107 | {ok, {Res1, Map1}} -> 108 | {ok, {Res2, Map2}} = load_optional(Res1, Map1, OptionalFields), 109 | Res3 = set_kv(Res2, Map2), 110 | {ok, Res3} 111 | end; 112 | 113 | from_defn(T = #?T{}) -> 114 | {ok, T}; 115 | 116 | from_defn(_) -> 117 | {error, not_map_or_record}. 118 | 119 | 120 | -spec has_succeeded(t()) -> boolean(). 121 | 122 | has_succeeded(Vert = #?T{}) -> 123 | case status(Vert) of 124 | done -> 125 | true; 126 | _ -> 127 | false 128 | end. 129 | 130 | 131 | -spec is_finished(t()) -> boolean(). 132 | 133 | is_finished(Vert = #?T{}) -> 134 | case status(Vert) of 135 | done -> 136 | true; 137 | failed -> 138 | true; 139 | cancelled -> 140 | true; 141 | _ -> 142 | false 143 | end. 144 | 145 | 146 | -spec succeed(t(), map()) -> t(). 147 | 148 | succeed(Vert = #?T{kv = Kv}, Result) -> 149 | Vert2 = set_kv(Vert, maps:merge(Kv, Result)), 150 | set_status(Vert2, done). 151 | 152 | 153 | -spec to_list(t()) -> [{atom(), any()}]. 154 | 155 | to_list(T = #?T{}) -> 156 | Fields = record_info(fields, ?T), 157 | [_Tag | Values] = tuple_to_list(T), 158 | lists:zip(Fields, Values). 159 | 160 | 161 | -spec args(t()) -> args_t(). 162 | 163 | args(#?T{args = Args}) -> 164 | Args. 165 | 166 | 167 | -spec deps(t()) -> deps_t(). 168 | 169 | deps(#?T{deps = Deps}) -> 170 | Deps. 171 | 172 | 173 | -spec dir(t()) -> dir_t(). 174 | 175 | dir(#?T{dir = Dir}) -> 176 | Dir. 177 | 178 | 179 | -spec id(t()) -> id_t(). 180 | 181 | id(#?T{id = Id}) -> 182 | Id. 183 | 184 | 185 | -spec kv(t()) -> kv_t(). 186 | 187 | kv(#?T{kv = Kv}) -> 188 | Kv. 189 | 190 | 191 | -spec module(t()) -> module_t(). 192 | 193 | module(#?T{module = Module}) -> 194 | Module. 195 | 196 | 197 | -spec name(t()) -> name_t(). 198 | 199 | name(#?T{name = Name}) -> 200 | Name. 201 | 202 | 203 | -spec reason(t()) -> reason_t(). 204 | 205 | reason(#?T{reason = Reason}) -> 206 | Reason. 207 | 208 | 209 | -spec status(t()) -> status_t(). 210 | 211 | status(#?T{status = Status}) -> 212 | Status. 213 | 214 | 215 | -spec timeout(t()) -> timeout_t(). 216 | 217 | timeout(#?T{timeout = Timeout}) -> 218 | Timeout. 219 | 220 | 221 | -spec set_args(t(), args_t()) -> t(). 222 | 223 | set_args(T = #?T{}, Args) -> 224 | T#?T{args = Args}. 225 | 226 | 227 | -spec set_deps(t(), deps_t()) -> t(). 228 | 229 | set_deps(T = #?T{}, Deps) -> 230 | T#?T{deps = Deps}. 231 | 232 | 233 | -spec set_dir(t(), dir_t()) -> t(). 234 | 235 | set_dir(T = #?T{}, Dir) -> 236 | T#?T{dir = Dir}. 237 | 238 | 239 | -spec set_id(t(), id_t()) -> t(). 240 | 241 | set_id(T = #?T{}, Id) -> 242 | T#?T{id = Id}. 243 | 244 | 245 | -spec set_kv(t(), kv_t()) -> t(). 246 | 247 | set_kv(T = #?T{}, Kv) -> 248 | T#?T{kv = Kv}. 249 | 250 | 251 | -spec set_module(t(), module_t()) -> t(). 252 | 253 | set_module(T = #?T{}, Module) -> 254 | T#?T{module = Module}. 255 | 256 | 257 | -spec set_name(t(), name_t()) -> t(). 258 | 259 | set_name(T = #?T{}, Name) -> 260 | T#?T{name = Name}. 261 | 262 | 263 | -spec set_reason(t(), reason_t()) -> t(). 264 | 265 | set_reason(T = #?T{}, Reason) -> 266 | T#?T{reason = Reason}. 267 | 268 | 269 | -spec set_status(t(), status_t()) -> t(). 270 | 271 | set_status(T = #?T{}, Status) -> 272 | T#?T{status = Status}. 273 | 274 | 275 | -spec set_timeout(t(), timeout_t()) -> t(). 276 | 277 | set_timeout(T = #?T{}, Timeout) -> 278 | T#?T{timeout = Timeout}. 279 | 280 | 281 | % private 282 | 283 | -type setter_t() :: fun((t(), any()) -> t()). 284 | 285 | -spec load_mandatory(t(), map(), [{atom(), setter_t()}]) -> 286 | {ok, {t(), map()}} | {error, any()}. 287 | 288 | load_mandatory(Vert, Map, FieldSetterPairs) -> 289 | load(Vert, Map, FieldSetterPairs, error). 290 | 291 | 292 | -spec load_optional(t(), map(), [{atom(), setter_t()}]) -> 293 | {ok, {t(), map()}}. 294 | 295 | load_optional(Vert, Map, FieldSetterPairs) -> 296 | load(Vert, Map, FieldSetterPairs, continue). 297 | 298 | 299 | -spec load(t(), map(), [{atom(), setter_t()}], error | continue) -> 300 | {ok, {t(), map()}} | {error, any()}. 301 | 302 | load(Vert = #?T{}, Map, [], _FailMode) -> 303 | {ok, {Vert, Map}}; 304 | 305 | load(Vert = #?T{}, Map, [{FieldName, Setter} | Rest], FailMode) -> 306 | case {Map, FailMode} of 307 | {#{FieldName := FieldVal}, _} -> 308 | Vert2 = Setter(Vert, FieldVal), 309 | Map2 = maps:remove(FieldName, Map), 310 | load(Vert2, Map2, Rest, FailMode); 311 | {_, error} -> 312 | {error, {missing_field, FieldName}}; 313 | {_, continue} -> 314 | load(Vert, Map, Rest, FailMode) 315 | end. 316 | -------------------------------------------------------------------------------- /test/verts/wrek_bad_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_bad_vert). 2 | 3 | -behavior(wrek_vert). 4 | 5 | -export([run/2]). 6 | 7 | 8 | run(_Args, Parent) -> 9 | Pwd = os:cmd("pwd") -- "\n", 10 | {ok, Fun} = wrek_vert:exec(Parent, Pwd, "exit 1"), 11 | ok = Fun(), % should badmatch 12 | {ok, #{}}. 13 | -------------------------------------------------------------------------------- /test/verts/wrek_echo_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_echo_vert). 2 | 3 | -behaviour(wrek_vert). 4 | -export([run/2]). 5 | 6 | run([Callback, Arg], Parent) -> 7 | Pwd = os:cmd("pwd") -- "\n", 8 | Cmd = io_lib:format("echo -n ~s", [Arg]), 9 | {ok, Fun} = wrek_vert:exec(Parent, Pwd, Cmd, [], Callback), 10 | ok = Fun(), 11 | {ok, #{}}. 12 | -------------------------------------------------------------------------------- /test/verts/wrek_get_sandbox_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_get_sandbox_vert). 2 | 3 | -behaviour(wrek_vert). 4 | 5 | -export([run/2]). 6 | 7 | run([Dep], Pid) -> 8 | Dir = wrek_vert:get_sandbox(Pid, Dep), 9 | wrek_vert:notify(Pid, {dir, Dir}), 10 | case Dir of 11 | undefined -> 12 | {error, undefined}; 13 | D -> 14 | {ok, #{dir => Dir}} 15 | end. 16 | -------------------------------------------------------------------------------- /test/verts/wrek_get_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_get_vert). 2 | 3 | -behaviour(wrek_vert). 4 | 5 | -export([run/2]). 6 | 7 | run(Pairs, Pid) -> 8 | Vals = lists:foldl(fun({Key, From}, Acc) -> 9 | Acc#{From => wrek_vert:get(Pid, From, Key)} 10 | end, #{}, Pairs), 11 | {ok, Vals}. 12 | -------------------------------------------------------------------------------- /test/verts/wrek_id_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_id_vert). 2 | 3 | -behaviour(wrek_vert). 4 | 5 | -export([run/2]). 6 | 7 | 8 | run([Arg], _Pid) -> 9 | {ok, #{result => Arg}}. 10 | -------------------------------------------------------------------------------- /test/verts/wrek_make_sandbox_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_make_sandbox_vert). 2 | 3 | -behaviour(wrek_vert). 4 | 5 | -export([run/2]). 6 | 7 | run(_, Pid) -> 8 | Dir = wrek_vert:make_sandbox(Pid), 9 | wrek_vert:notify(Pid, {dir, Dir}), 10 | {ok, #{dir => Dir}}. 11 | -------------------------------------------------------------------------------- /test/verts/wrek_reuse_sandbox_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_reuse_sandbox_vert). 2 | 3 | -behaviour(wrek_vert). 4 | 5 | -export([run/2]). 6 | 7 | run([Who], Pid) -> 8 | {ok, Dir} = wrek_vert:reuse_sandbox(Pid, Who), 9 | wrek_vert:notify(Pid, {dir, Dir}), 10 | {ok, #{dir => Dir}}. 11 | -------------------------------------------------------------------------------- /test/verts/wrek_sleep_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_sleep_vert). 2 | 3 | -behaviour(wrek_vert). 4 | -export([run/2]). 5 | 6 | run(_Args, Parent) -> 7 | Pwd = os:cmd("pwd") -- "\n", 8 | {ok, Fun} = wrek_vert:exec(Parent, Pwd, "sleep 9000"), 9 | ok = Fun(), 10 | {ok, #{}}. 11 | -------------------------------------------------------------------------------- /test/verts/wrek_true_vert.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_true_vert). 2 | 3 | -behavior(wrek_vert). 4 | 5 | -export([run/2]). 6 | 7 | 8 | run(_Args, Parent) -> 9 | Pwd = os:cmd("pwd") -- "\n", 10 | {ok, Fun} = wrek_vert:exec(Parent, Pwd, "true"), 11 | ok = Fun(), 12 | {ok, #{}}. 13 | -------------------------------------------------------------------------------- /test/wrek_cb_event_handler.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_cb_event_handler). 2 | -include("wrek_event.hrl"). 3 | 4 | -export([code_change/3, 5 | handle_call/2, 6 | handle_event/2, 7 | handle_info/2, 8 | init/1, 9 | terminate/2]). 10 | -behaviour(gen_event). 11 | 12 | 13 | -record(state, { 14 | caller = undefined :: pid(), 15 | count = 0 :: integer(), 16 | evts = [] :: [wrek_event()], 17 | fail_mode = total :: total | partial 18 | }). 19 | 20 | 21 | %% callbacks 22 | 23 | code_change(_OldVsn, State, _Extra) -> 24 | {ok, State}. 25 | 26 | handle_call(get, State) -> 27 | {ok, State#state.count, State}; 28 | 29 | handle_call(_, State) -> 30 | {ok, ok, State}. 31 | 32 | 33 | handle_event(#wrek_event{type = exec, msg = {_, Data}}, Pid) -> 34 | Pid ! {event_handler, Data}, 35 | {ok, Pid}; 36 | 37 | handle_event(Evt, State) -> 38 | {ok, State}. 39 | 40 | 41 | handle_info(_, State) -> 42 | {ok, State}. 43 | 44 | 45 | init([CallbackPid]) -> 46 | {ok, CallbackPid}. 47 | 48 | 49 | terminate(_, _State) -> 50 | ok. 51 | -------------------------------------------------------------------------------- /test/wrek_test_handler.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_test_handler). 2 | -include("wrek_event.hrl"). 3 | 4 | -export([code_change/3, 5 | handle_call/2, 6 | handle_event/2, 7 | handle_info/2, 8 | init/1, 9 | terminate/2]). 10 | -behaviour(gen_event). 11 | 12 | 13 | -record(state, { 14 | caller = undefined :: pid(), 15 | count = 0 :: integer(), 16 | evts = [] :: [wrek_event()], 17 | fail_mode = total :: total | partial 18 | }). 19 | 20 | 21 | %% callbacks 22 | 23 | code_change(_OldVsn, State, _Extra) -> 24 | {ok, State}. 25 | 26 | handle_call(get, State) -> 27 | {ok, State#state.count, State}; 28 | 29 | handle_call(_, State) -> 30 | {ok, ok, State}. 31 | 32 | 33 | handle_event(Evt = #wrek_event{type = {wrek, error}}, 34 | State = #state{fail_mode = total}) -> 35 | finish(Evt, State); 36 | 37 | handle_event(Evt = #wrek_event{type = {wrek, done}}, State) -> 38 | finish(Evt, State); 39 | 40 | handle_event(Evt, State = #state{count = Count, evts = Evts}) -> 41 | {ok, State#state{count = Count + 1, evts = [Evt | Evts]}}. 42 | 43 | 44 | handle_info(_, State) -> 45 | {ok, State}. 46 | 47 | 48 | init([FailMode, Caller]) -> 49 | {ok, #state{caller = Caller, fail_mode = FailMode}}. 50 | 51 | 52 | terminate(_, _State) -> 53 | ok. 54 | 55 | 56 | %% private 57 | 58 | finish(Evt, State) -> 59 | #state{ 60 | caller = Caller, 61 | count = Count, 62 | evts = Evts 63 | } = State, 64 | Caller ! #{count => Count + 1, evts => [Evt | Evts]}, 65 | remove_handler. 66 | -------------------------------------------------------------------------------- /test/wrek_tests.erl: -------------------------------------------------------------------------------- 1 | -module(wrek_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | -include("wrek_event.hrl"). 4 | 5 | from_verts_ok_test() -> 6 | Verts = #{ 7 | foo => ok_v([]), 8 | bar => ok_v([foo]), 9 | baz => ok_v([bar]) 10 | }, 11 | {ok, Wrek} = wrek_t:from_verts(Verts), 12 | 13 | Dag = element(3, Wrek), 14 | 15 | ?assertEqual([foo, bar, baz], 16 | digraph_utils:topsort(Dag)). 17 | 18 | from_verts_cycle_test() -> 19 | Cycle = #{ 20 | foo => ok_v([baz]), 21 | bar => ok_v([foo]), 22 | baz => ok_v([bar]) 23 | }, 24 | ?assertMatch({error, {bad_edge, _}}, 25 | wrek_t:from_verts(Cycle)). 26 | 27 | from_verts_missing_dep_test() -> 28 | Verts = #{bar => ok_v([foo])}, 29 | 30 | ?assertMatch({error, {bad_vertex, _}}, 31 | wrek_t:from_verts(Verts)). 32 | 33 | ok_test() -> 34 | error_logger:tty(false), 35 | exec:start(), 36 | application:start(wrek), 37 | 38 | VertMap = #{ 39 | one => ok_v([]), 40 | two => ok_v([one]), 41 | two_and_a_half => ok_v([one]), 42 | three => ok_v([two]) 43 | }, 44 | 45 | {ok, Pid} = wrek:start(VertMap), 46 | 47 | MonitorRef = erlang:monitor(process, Pid), 48 | 49 | Atom = receive 50 | {'DOWN', MonitorRef, process, Pid, A} -> A 51 | end, 52 | 53 | ?assertEqual(normal, Atom). 54 | 55 | not_ok_test() -> 56 | error_logger:tty(false), 57 | exec:start(), 58 | application:start(wrek), 59 | 60 | VertMap = #{ 61 | one => ok_v([]), 62 | two => ok_v([one]), 63 | two_and_a_half => ok_v([one]), 64 | three => bad_v([two]) 65 | }, 66 | 67 | {ok, Pid} = wrek:start(VertMap), 68 | 69 | MonitorRef = erlang:monitor(process, Pid), 70 | 71 | Atom = receive 72 | {'DOWN', MonitorRef, process, Pid, A} -> A 73 | end, 74 | 75 | ?assertMatch({error, _}, Atom). 76 | 77 | event_ok_test() -> 78 | exec:start(), 79 | application:start(wrek), 80 | 81 | {ok, EvMgr} = gen_event:start_link({local, wrek_test_manager}), 82 | gen_event:add_handler(EvMgr, wrek_test_handler, [total, self()]), 83 | 84 | VertMap = #{ 85 | one => ok_v([]), 86 | two => ok_v([one]), 87 | two_and_a_half => ok_v([one]), 88 | three => ok_v([two]) 89 | }, 90 | 91 | {ok, _Pid} = wrek:start(VertMap, [{event_manager, EvMgr}]), 92 | 93 | Events = receive 94 | #{evts := Evts} -> Evts 95 | end, 96 | 97 | gen_event:stop(EvMgr), 98 | 99 | WrekStarts = 100 | [E || E = #wrek_event{type = {wrek, start}} <- Events], 101 | StartingVerts = 102 | [E || E = #wrek_event{msg = {starting_vert, _}} <- Events], 103 | VertStarts = 104 | [E || E = #wrek_event{type = {vert, start}} <- Events], 105 | VertDones = 106 | [E || E = #wrek_event{type = {vert, done}} <- Events], 107 | WrekDones = 108 | [E || E = #wrek_event{type = {wrek, done}} <- Events], 109 | 110 | ?assertEqual(1, length(WrekStarts)), 111 | ?assertEqual(4, length(StartingVerts)), 112 | ?assertEqual(4, length(VertStarts)), 113 | ?assertEqual(4, length(VertDones)), 114 | ?assertEqual(1, length(WrekDones)). 115 | 116 | event_bad_test() -> 117 | exec:start(), 118 | application:start(wrek), 119 | 120 | {ok, EvMgr} = gen_event:start_link({local, wrek_test_manager}), 121 | gen_event:add_handler(EvMgr, wrek_test_handler, [total, self()]), 122 | 123 | VertMap = #{ 124 | one => ok_v([]), 125 | two => ok_v([one]), 126 | two_and_a_half => ok_v([one]), 127 | three => bad_v([two]) 128 | }, 129 | 130 | {ok, _Pid} = wrek:start(VertMap, [{event_manager, EvMgr}]), 131 | 132 | Events = receive 133 | #{evts := Evts} -> Evts 134 | end, 135 | 136 | gen_event:stop(EvMgr), 137 | 138 | WrekStarts = 139 | [E || E = #wrek_event{type = {wrek, start}} <- Events], 140 | StartingVerts = 141 | [E || E = #wrek_event{msg = {starting_vert, _}} <- Events], 142 | VertStarts = 143 | [E || E = #wrek_event{type = {vert, start}} <- Events], 144 | VertDones = 145 | [E || E = #wrek_event{type = {vert, done}} <- Events], 146 | WrekErrors = 147 | [E || E = #wrek_event{type = {wrek, error}} <- Events], 148 | 149 | ?assertEqual(1, length(WrekStarts)), 150 | ?assertEqual(4, length(StartingVerts)), 151 | ?assertEqual(4, length(VertStarts)), 152 | ?assertEqual(4, length(VertDones)), 153 | ?assertEqual(1, length(WrekErrors)). 154 | 155 | event_time_diff_test() -> 156 | E1 = #wrek_event{}, 157 | E2 = #wrek_event{}, 158 | ?assert(wrek_event:time_diff(E1, E2) >= 0). 159 | 160 | 161 | fail_cancels_transitive_closure_test() -> 162 | application:start(wrek), 163 | 164 | VertMap = #{ 165 | one => bad_v([]), 166 | two => ok_v([one]), 167 | three => ok_v([two]) 168 | }, 169 | 170 | {ok, EvMgr} = gen_event:start_link({local, wrek_test_manager}), 171 | gen_event:add_handler(EvMgr, wrek_test_handler, [partial, self()]), 172 | 173 | Opts = [{event_manager, EvMgr}, {failure_mode, partial}], 174 | {ok, _Pid} = wrek:start(VertMap, Opts), 175 | 176 | Msgs = receive 177 | #{evts := M} -> M 178 | end, 179 | 180 | gen_event:stop(EvMgr), 181 | 182 | CancelMsgs = lists:filter(fun 183 | (#wrek_event{msg = {vert_cancelled, _}}) -> true; 184 | (_) -> false 185 | end, Msgs), 186 | 187 | ?assertEqual(2, length(CancelMsgs)). 188 | 189 | 190 | partial_success_test() -> 191 | application:start(wrek), 192 | 193 | VertMap = #{ 194 | one => ok_v([]), 195 | bad_two => bad_v([one]), 196 | bad_three => bad_v([bad_two]), 197 | ok_two => ok_v([one]), 198 | ok_three => ok_v([ok_two]), 199 | four => ok_v([ok_three, bad_three]) 200 | }, 201 | 202 | {ok, EvMgr} = gen_event:start_link({local, wrek_test_manager}), 203 | gen_event:add_handler(EvMgr, wrek_test_handler, [partial, self()]), 204 | 205 | Opts = [{event_manager, EvMgr}, {failure_mode, partial}], 206 | {ok, _Pid} = wrek:start(VertMap, Opts), 207 | 208 | Events = receive 209 | #{evts := M} -> M 210 | end, 211 | 212 | gen_event:stop(EvMgr), 213 | 214 | SuccessMsgs = 215 | [E || E = #wrek_event{type = {vert, done}, msg = {ok, _}} <- Events], 216 | 217 | CancelMsgs = 218 | [E || E = #wrek_event{msg = {vert_cancelled, _}} <- Events], 219 | 220 | FailMsgs = 221 | [E || E = #wrek_event{type = {_, error}, msg = {vert, _}} <- Events], 222 | 223 | % one, ok_two, ok_three 224 | ?assertEqual(3, length(SuccessMsgs)), 225 | % bad_two 226 | ?assertEqual(1, length(FailMsgs)), 227 | % bad_three, four 228 | ?assertEqual(2, length(CancelMsgs)). 229 | 230 | 231 | timeout_test() -> 232 | VertDefn = #{module => wrek_sleep_vert, args => [], deps => [], timeout => 10}, 233 | VertMap = #{cat => VertDefn}, 234 | 235 | {ok, EvMgr} = gen_event:start_link({local, wrek_test_manager}), 236 | gen_event:add_handler(EvMgr, wrek_test_handler, [total, self()]), 237 | 238 | {ok, _Pid} = wrek:start(VertMap, [{event_manager, EvMgr}]), 239 | 240 | Map = receive 241 | M -> M 242 | end, 243 | 244 | gen_event:stop(EvMgr), 245 | 246 | FailMsgs = lists:filter(fun 247 | (#wrek_event{type = {wrek, error}, msg = {vert, _}}) -> true; 248 | (#wrek_event{type = {vert, done}, msg = timeout}) -> true; 249 | (_) -> false 250 | end, maps:get(evts, Map)), 251 | 252 | ?assertEqual(2, length(FailMsgs)). 253 | 254 | get_test() -> 255 | application:start(wrek), 256 | 257 | IdVerts = [1, 2, 3], 258 | Pairs = [{result, From} || From <- IdVerts] ++ [{extra, 4}], 259 | 260 | VertMap = #{ 261 | 1 => id_v(1, []), 262 | 2 => id_v(2, [1]), 263 | 3 => id_v(3, [1]), 264 | 4 => #{module => wrek_true_vert, args => [], deps => [1], extra => 4}, 265 | get => get_v(Pairs) 266 | }, 267 | 268 | {ok, EvMgr} = gen_event:start_link({local, wrek_test_manager}), 269 | gen_event:add_handler(EvMgr, wrek_test_handler, [total, self()]), 270 | 271 | Opts = [{event_manager, EvMgr}, {failure_mode, total}], 272 | {ok, _Pid} = wrek:start(VertMap, Opts), 273 | 274 | Msgs = receive 275 | #{evts := M} -> M 276 | end, 277 | 278 | gen_event:stop(EvMgr), 279 | 280 | IdNameMap = lists:foldl(fun 281 | (#wrek_event{id = Id, type = {vert, start}, msg = {Name, _, _}}, Acc) -> 282 | Acc#{Id => Name}; 283 | (_, Acc) -> Acc 284 | end, #{}, Msgs), 285 | 286 | ReturnVals = lists:foldl(fun 287 | (#wrek_event{id = Id, type = {vert, done}, msg = {ok, Val}}, Acc) -> 288 | #{Id := Name} = IdNameMap, 289 | Acc#{Name => Val}; 290 | (_, Acc) -> Acc 291 | end, #{}, Msgs), 292 | 293 | GetExpect = #{ 294 | 1 => 1, 295 | 2 => 2, 296 | 3 => 3, 297 | 4 => 4 298 | }, 299 | 300 | ?assertMatch(#{1 := #{result := 1}}, ReturnVals), 301 | ?assertMatch(#{2 := #{result := 2}}, ReturnVals), 302 | ?assertMatch(#{3 := #{result := 3}}, ReturnVals), 303 | ?assertMatch(#{get := GetExpect}, ReturnVals). 304 | 305 | sandbox_test() -> 306 | application:start(wrek), 307 | 308 | VertMap = #{ 309 | 1 => #{module => wrek_make_sandbox_vert, args => [], deps => []}, 310 | 2 => #{module => wrek_get_sandbox_vert, args => [1], deps => [1]}, 311 | 3 => #{module => wrek_reuse_sandbox_vert, args => [1], deps => [1]}, 312 | 4 => #{module => wrek_get_sandbox_vert, args => [3], deps => [3]} 313 | }, 314 | 315 | {ok, EvMgr} = gen_event:start_link({local, wrek_test_manager}), 316 | gen_event:add_handler(EvMgr, wrek_test_handler, [total, self()]), 317 | 318 | Opts = [{event_manager, EvMgr}, {failure_mode, total}], 319 | {ok, _Pid} = wrek:start(VertMap, Opts), 320 | 321 | Msgs = receive 322 | #{evts := M} -> M 323 | end, 324 | 325 | gen_event:stop(EvMgr), 326 | 327 | Notifs = lists:foldl(fun 328 | (#wrek_event{type = {vert, msg}, msg = {dir, D}}, Acc) -> 329 | [D | Acc]; 330 | (_, Acc) -> 331 | Acc 332 | end, [], Msgs), 333 | 334 | ?assertEqual(4, length(Notifs)), 335 | ?assertEqual(lists:usort(Notifs), [lists:nth(1, Notifs)]). 336 | 337 | custom_exec_callback_test() -> 338 | Self = self(), 339 | Callback = fun({_, Data}) -> 340 | Self ! {event_fun, Data} 341 | end, 342 | 343 | {ok, EvMgr} = gen_event:start_link(), 344 | gen_event:add_handler(EvMgr, wrek_cb_event_handler, [Self]), 345 | 346 | Dag = #{cb => #{module => wrek_echo_vert, args => [Callback, "good"], deps => []}}, 347 | 348 | wrek:start(Dag, [{event_manager, EvMgr}]), 349 | 350 | EventFunMsg = receive 351 | {event_fun, Efm} -> Efm 352 | after 353 | 500 -> bad 354 | end, 355 | 356 | EventHandlerMsg = receive 357 | {event_handler, Ehm} -> Ehm 358 | after 359 | 500 -> bad 360 | end, 361 | 362 | gen_event:stop(EvMgr), 363 | 364 | ?assertEqual(EventFunMsg, <<"good">>), 365 | ?assertEqual(EventHandlerMsg, <<"good">>), 366 | ?assertEqual(EventFunMsg, EventHandlerMsg). 367 | 368 | wrek_timeout_test() -> 369 | exec:start(), 370 | application:start(wrek), 371 | 372 | {ok, EvMgr} = gen_event:start_link({local, wrek_test_manager}), 373 | gen_event:add_handler(EvMgr, wrek_test_handler, [total, self()]), 374 | 375 | VertMap = #{ 376 | one => ok_v([]), 377 | two => ok_v([one]), 378 | two_and_a_half => ok_v([one]), 379 | three => ok_v([two]) 380 | }, 381 | 382 | {ok, _Pid} = wrek:start(VertMap, [{event_manager, EvMgr}, {global_timeout, 0}]), 383 | 384 | Events = receive 385 | #{evts := Evts} -> Evts 386 | end, 387 | 388 | gen_event:stop(EvMgr), 389 | 390 | WrekStarts = 391 | [E || E = #wrek_event{type = {wrek, start}} <- Events], 392 | StartingVerts = 393 | [E || E = #wrek_event{msg = {starting_vert, _}} <- Events], 394 | VertStarts = 395 | [E || E = #wrek_event{type = {vert, start}} <- Events], 396 | VertDones = 397 | [E || E = #wrek_event{type = {vert, done}} <- Events], 398 | WrekDones = 399 | [E || E = #wrek_event{type = {wrek, done}} <- Events], 400 | 401 | ?assertEqual(1, length(WrekStarts)), 402 | ?assertEqual(1, length(StartingVerts)), 403 | ?assertEqual(1, length(VertStarts)), 404 | ?assertEqual(0, length(VertDones)), 405 | ?assertEqual(0, length(WrekDones)). 406 | 407 | 408 | %% private 409 | 410 | ok_v(Deps) -> 411 | #{module => wrek_true_vert, args => [], deps => Deps}. 412 | 413 | bad_v(Deps) -> 414 | #{module => wrek_bad_vert, args => [], deps => Deps}. 415 | 416 | id_v(Val, Deps) -> 417 | #{module => wrek_id_vert, args => [Val], deps => Deps}. 418 | 419 | get_v(Pairs) -> 420 | Deps = [Dep || {_K, Dep} <- Pairs], 421 | #{module => wrek_get_vert, args => Pairs, deps => Deps}. 422 | --------------------------------------------------------------------------------