diff options
author | Adam Kocoloski <kocolosk@apache.org> | 2021-01-21 15:50:54 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-21 15:50:54 -0500 |
commit | 8a5323cc97ecb085cf2dcf340433180809ca6820 (patch) | |
tree | 1c3a947906480ecf1ec7a3d9bb4a6cad38530bd9 /dev | |
parent | 476787b0f2b34d56870c54c2b4d680151fd088e6 (diff) | |
download | couchdb-8a5323cc97ecb085cf2dcf340433180809ca6820.tar.gz |
Simplify and speedup dev node startup (#3337)
* Simplify and speedup dev node startup
This patch introduces an escript that generates an Erlang .boot script
to start CouchDB using the in-place .beam files produced by the compile
phase of the build. This allows us to radically simplify the boot
process as Erlang computes the optimal order for loading the necessary
modules.
In addition to the simplification this approach offers a significant
speedup when working inside a container environment. In my test with
the stock .devcontainer it reduces startup time from about 75 seconds
down to under 5 seconds.
* Rename boot_node to monitor_parent
* Add formatting suggestions from python-black
Co-authored-by: Paul J. Davis <paul.joseph.davis@gmail.com>
Diffstat (limited to 'dev')
-rw-r--r-- | dev/boot_node.erl | 148 | ||||
-rwxr-xr-x | dev/make_boot_script | 9 | ||||
-rw-r--r-- | dev/monitor_parent.erl | 43 | ||||
-rwxr-xr-x | dev/run | 17 |
4 files changed, 65 insertions, 152 deletions
diff --git a/dev/boot_node.erl b/dev/boot_node.erl deleted file mode 100644 index 922a5ccb6..000000000 --- a/dev/boot_node.erl +++ /dev/null @@ -1,148 +0,0 @@ -% Licensed under the Apache License, Version 2.0 (the "License"); you may not -% use this file except in compliance with the License. You may obtain a copy of -% the License at -% -% http://www.apache.org/licenses/LICENSE-2.0 -% -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -% License for the specific language governing permissions and limitations under -% the License. - --module(boot_node). - --export([start/0]). - - -start() -> - monitor_parent(), - Apps = load_apps(), - Deps = load_deps(Apps), - start_all_apps(Deps). - - -monitor_parent() -> - {ok, [[PPid]]} = init:get_argument(parent_pid), - spawn(fun() -> monitor_parent(PPid) end). - - -monitor_parent(PPid) -> - timer:sleep(1000), - case os:type() of - {unix, _} -> - case os:cmd("kill -0 " ++ PPid) of - "" -> - monitor_parent(PPid); - _Else -> - % Assume _Else is a no such process error - init:stop() - end; - {win32, _} -> - Fmt = "tasklist /fi \"PID eq ~s\" /fo csv /nh", - Retval = os:cmd(io_lib:format(Fmt, [PPid])), - case re:run(Retval, "^\"python.exe\",*") of - {match, _} -> - monitor_parent(PPid); - nomatch -> - init:stop() - end - end. - - -load_apps() -> - {ok, [[Config]]} = init:get_argument(reltool_config), - {ok, Terms} = file:consult(Config), - load_apps(Terms). - - -load_apps([]) -> - erlang:error(failed_to_load_apps); -load_apps([{sys, Terms} | _]) -> - load_apps(Terms); -load_apps([{rel, "couchdb", _Vsn, Apps} | _]) -> - Apps; -load_apps([_ | Rest]) -> - load_apps(Rest). - - -load_deps(Apps) -> - load_deps(Apps, dict:new()). - - -load_deps([], Deps) -> - Deps; -load_deps([App | Rest], Deps) -> - load_app(App), - case application:get_key(App, applications) of - {ok, AppDeps0} -> - NewDeps = dict:store(App, AppDeps0, Deps), - Filter = fun(A) -> not dict:is_key(A, Deps) end, - AppDeps = lists:filter(Filter, AppDeps0), - load_deps(AppDeps ++ Rest, NewDeps); - _ -> - NewDeps = dict:store(App, [], Deps), - load_deps(Rest, NewDeps) - end. - - -load_app(App) -> - case application:load(App) of - ok -> - case application:get_key(App, modules) of - {ok, Modules} -> - lists:foreach(fun(Mod) -> - case load_app_module(Mod) of - ok -> ok; - E -> io:format("~p = load_app_module(~p)~n", [E, Mod]) - end - end, Modules); - undefined -> - ok - end; - {error, {already_loaded, App}} -> - ok; - Error -> - Error - end. - - -load_app_module(Mod) -> - case code:is_loaded(Mod) of - {file, _} -> - ok; - _ -> - case code:load_file(Mod) of - {module, Mod} -> - ok; - Error -> - Error - end - end. - - -start_all_apps(Deps) -> - lists:foldl(fun(App, Started) -> - start_app(App, Deps, Started) - end, [], dict:fetch_keys(Deps)). - - -start_app(App, Deps, Started) -> - case lists:member(App, Started) of - true -> - Started; - false -> - AppDeps = dict:fetch(App, Deps), - NowStarted = lists:foldl(fun(Dep, Acc) -> - start_app(Dep, Deps, Acc) - end, Started, AppDeps), - case application:start(App) of - ok -> - [App | NowStarted]; - {error, {already_started,App}} -> - % Kernel causes this - [App | NowStarted]; - Else -> - erlang:error(Else) - end - end. diff --git a/dev/make_boot_script b/dev/make_boot_script new file mode 100755 index 000000000..549dd9a07 --- /dev/null +++ b/dev/make_boot_script @@ -0,0 +1,9 @@ +#!/usr/bin/env escript + +main(_) -> + {ok, Server} = reltool:start_server([ + {config, "../rel/reltool.config"} + ]), + {ok, Release} = reltool:get_rel(Server, "couchdb"), + ok = file:write_file("devnode.rel", io_lib:format("~p.~n", [Release])), + ok = systools:make_script("devnode", [local]). diff --git a/dev/monitor_parent.erl b/dev/monitor_parent.erl new file mode 100644 index 000000000..382f37e9c --- /dev/null +++ b/dev/monitor_parent.erl @@ -0,0 +1,43 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-module(monitor_parent). + +-export([start/0]). + + +start() -> + {ok, [[PPid]]} = init:get_argument(parent_pid), + spawn(fun() -> monitor_parent(PPid) end). + + +monitor_parent(PPid) -> + timer:sleep(1000), + case os:type() of + {unix, _} -> + case os:cmd("kill -0 " ++ PPid) of + "" -> + monitor_parent(PPid); + _Else -> + % Assume _Else is a no such process error + init:stop() + end; + {win32, _} -> + Fmt = "tasklist /fi \"PID eq ~s\" /fo csv /nh", + Retval = os:cmd(io_lib:format(Fmt, [PPid])), + case re:run(Retval, "^\"python.exe\",*") of + {match, _} -> + monitor_parent(PPid); + nomatch -> + init:stop() + end + end. @@ -101,6 +101,7 @@ def setup(): setup_logging(ctx) setup_dirs(ctx) check_beams(ctx) + check_boot_script(ctx) setup_configs(ctx) return ctx @@ -268,6 +269,14 @@ def check_beams(ctx): sp.check_call(["erlc", "-o", ctx["devdir"] + os.sep, fname]) +@log("Ensure Erlang boot script exists") +def check_boot_script(ctx): + if not os.path.exists(os.path.join(ctx["devdir"], "devnode.boot")): + env = os.environ.copy() + env["ERL_LIBS"] = os.path.join(ctx["rootdir"], "src") + sp.check_call(["escript", "make_boot_script"], env=env, cwd=ctx["devdir"]) + + @log("Prepare configuration files") def setup_configs(ctx): for idx, node in enumerate(ctx["nodes"]): @@ -592,10 +601,9 @@ def set_boot_env(ctx): @log("Start node {node}") def boot_node(ctx, node): - erl_libs = os.path.join(ctx["rootdir"], "src") set_boot_env(ctx) env = os.environ.copy() - env["ERL_LIBS"] = os.pathsep.join([erl_libs]) + env["ERL_LIBS"] = os.path.join(ctx["rootdir"], "src") node_etcdir = os.path.join(ctx["devdir"], "lib", node, "etc") reldir = os.path.join(ctx["rootdir"], "rel") @@ -614,11 +622,12 @@ def boot_node(ctx, node): os.path.join(reldir, "reltool.config"), "-parent_pid", str(os.getpid()), + "-boot", + os.path.join(ctx["devdir"], "devnode"), "-pa", ctx["devdir"], + "-s monitor_parent", ] - cmd += [p[:-1] for p in glob.glob(erl_libs + "/*/")] - cmd += ["-s", "boot_node"] if ctx["reset_logs"]: mode = "wb" else: |