reboot_system_after_config is opt-in if using config/runtime.exs

This commit is contained in:
José Valim
2020-09-03 12:59:43 +02:00
parent a19d8f05e6
commit ca95b19b0f
7 changed files with 73 additions and 52 deletions
+9 -8
View File
@@ -109,11 +109,13 @@ defmodule Config.Provider do
:providers,
:config_path,
extra_config: [],
prune_after_boot: false,
reboot_after_config: true,
prune_runtime_sys_config_after_boot: false,
reboot_system_after_config: false,
validate_compile_env: false
]
@reserved_apps [:kernel, :stdlib]
@doc """
Validates a `t:config_path/0`.
"""
@@ -194,7 +196,6 @@ defmodule Config.Provider do
defp boot_providers(path, provider, reboot_config, reboot_fun) do
validate_no_cyclic_boot!(path)
loaded_applications = :application.loaded_applications()
original_config = read_config!(path)
config =
@@ -202,16 +203,16 @@ defmodule Config.Provider do
|> Config.__merge__(provider.extra_config)
|> run_providers(provider)
if provider.reboot_after_config do
if provider.reboot_system_after_config do
config
|> Config.__merge__(reboot_config)
|> write_config!(path)
reboot_fun.()
else
for {app, _, _} <- loaded_applications, config[app] != original_config[app] do
for app <- @reserved_apps, config[app] != original_config[app] do
abort("""
Cannot configure #{inspect(app)} because :reboot_after_config has been set \
Cannot configure #{inspect(app)} because :reboot_system_after_config has been set \
to false and #{inspect(app)} has already been loaded, meaning any further \
configuration won't have an effect.
@@ -316,8 +317,8 @@ defmodule Config.Provider do
Process.sleep(:infinity)
end
defp booted_value(%{prune_after_boot: true}, path), do: {:booted, path}
defp booted_value(%{prune_after_boot: false}, _path), do: {:booted, nil}
defp booted_value(%{prune_runtime_sys_config_after_boot: true}, path), do: {:booted, path}
defp booted_value(%{prune_runtime_sys_config_after_boot: false}, _path), do: {:booted, nil}
defp validate_no_cyclic_boot!(path) do
if System.get_env("ELIXIR_CONFIG_PROVIDER_BOOTED") do
@@ -106,7 +106,7 @@ defmodule Config.ProviderTest do
end
test "returns :booted if already booted and prunes config file" do
init_and_assert_boot(prune_after_boot: true)
init_and_assert_boot(prune_runtime_sys_config_after_boot: true)
Application.put_all_env(Keyword.take(consult(@sys_config), [:elixir]))
assert boot() == :booted
refute_received :restart
@@ -115,7 +115,7 @@ defmodule Config.ProviderTest do
test "returns :booted if already booted and runs validate_compile_env" do
init_and_assert_boot(
prune_after_boot: true,
prune_runtime_sys_config_after_boot: true,
validate_compile_env: [{:elixir, [:unknown], {:ok, :value}}]
)
@@ -135,14 +135,15 @@ defmodule Config.ProviderTest do
test "returns without rebooting" do
reader = {Config.Reader, fixture_path("configs/kernel.exs")}
init = Config.Provider.init([reader], @sys_config, reboot_after_config: false)
init = Config.Provider.init([reader], @sys_config, reboot_system_after_config: false)
Application.put_all_env(init)
assert capture_abort(fn ->
Provider.boot(fn ->
raise "should not be called"
end)
end) =~ "Cannot configure :kernel because :reboot_after_config has been set to false"
end) =~
"Cannot configure :kernel because :reboot_system_after_config has been set to false"
# Make sure values before and after match
write_sys_config!(kernel: [elixir_reboot: true])
@@ -167,7 +168,7 @@ defmodule Config.ProviderTest do
end
defp init_and_assert_boot(opts \\ []) do
init(opts)
init(opts ++ [reboot_system_after_config: true])
boot()
assert_received :restart
end
+6 -4
View File
@@ -429,12 +429,14 @@ defmodule Mix.Release do
defp merge_provider_config(release, sys_config, config_path) do
{reboot?, extra_config, initial_config} = start_distribution(release)
prune_after_boot = Keyword.get(release.options, :prune_runtime_sys_config_after_boot, false)
prune_runtime_sys_config_after_boot =
Keyword.get(release.options, :prune_runtime_sys_config_after_boot, false)
opts = [
extra_config: initial_config,
prune_after_boot: prune_after_boot,
reboot_after_config: reboot?,
prune_runtime_sys_config_after_boot: prune_runtime_sys_config_after_boot,
reboot_system_after_config: reboot?,
validate_compile_env: validate_compile_env(release)
]
@@ -458,7 +460,7 @@ defmodule Mix.Release do
end
defp start_distribution(%{options: opts}) do
reboot? = Keyword.get(opts, :reboot_system_after_config, true)
reboot? = Keyword.get(opts, :reboot_system_after_config, false)
early_distribution? = Keyword.get(opts, :start_distribution_during_config, false)
if not reboot? or early_distribution? do
+13 -6
View File
@@ -70,19 +70,26 @@ defmodule Mix.Tasks.Loadconfig do
Mix.shell().error("""
Cannot configure base applications: #{inspect(reserved_apps)}
These applications are already started by the time Mix loads and
therefore these configurations have no effect.
These applications are already started by the time the configuration
executes and these configurations have no effect.
If you want to configure these applications for a release, wrap
them in a condition, such as:
If you want to configure these applications for a release, you can
specify them in your vm.args file:
-kernel config_key config_value
Alternatively, if you must configure them dynamically, you can wrap
them in a conditional block in your config files:
if System.get_env("RELEASE_MODE") do
config :kernel, ...
end
Alternatively, specify the configuration in your vm.args file:
and then configure your releases to reboot after configuration:
-kernel config_key config_value
releases: [
my_app: [reboot_system_after_config: true]
]
This happened when loading #{Path.relative_to_cwd(file)} or
one of its imports.
+31 -22
View File
@@ -650,28 +650,29 @@ defmodule Mix.Tasks.Release do
The following options can be set inside your releases key in your `mix.exs`
to control how config providers work:
* `:start_distribution_during_config` - on Erlang/OTP 22+, releases
only start the Erlang VM distribution features after the config files
are evaluated. You can set it to `true` if you need distribution during
configuration. Defaults to `false`.
* `:reboot_system_after_config` - every time your release is configured,
the system is rebooted to allow the new configuration to take place.
You can set this option to `false` to disable the rebooting for applications
that are sensitive to boot time but, in doing so, note you won't be able
to configure system applications, such as `:kernel`, `:stdlib` and `:elixir`
itself. Defaults to `true`.
to configure system applications, such as `:kernel` and `:stdlib`.
Defaults to `true` if using the deprecated `config/releases.exs`,
`false` otherwise.
* `:prune_runtime_sys_config_after_boot` - every time your system boots,
the release will write a config file to your tmp directory. These
configuration files are generally small. But if you are concerned with
disk space or if you have other restrictions, you can ask the system to
remove said config files after boot. The downside is that you will no
longer be able to restart the system internally (neither via
`System.restart/0` nor `bin/RELEASE_NAME restart`). If you need a restart,
* `:prune_runtime_sys_config_after_boot` - if `:reboot_system_after_config`
is set, every time your system boots, the release will write a config file
to your tmp directory. These configuration files are generally small.
But if you are concerned with disk space or if you have other restrictions,
you can ask the system to remove said config files after boot. The downside
is that you will no longer be able to restart the system internally (neither
via `System.restart/0` nor `bin/RELEASE_NAME restart`). If you need a restart,
you will have to terminate the Operating System process and start a new
one. Defaults to `false`.
* `:start_distribution_during_config` - if `:reboot_system_after_config` is
set, releases only start the Erlang VM distribution features after the config
files are evaluated. You can set it to `true` if you need distribution during
configuration. Defaults to `false` from Erlang/OTP 22+.
* `:config_providers` - a list of tuples with custom config providers.
See `Config.Provider` for more information. Defaults to `[]`.
@@ -1152,6 +1153,7 @@ defmodule Mix.Tasks.Release do
version_path = release.version_path
File.rm_rf!(version_path)
File.mkdir_p!(version_path)
release = maybe_add_config_reader_provider(config, release, version_path)
consolidation_path =
if config[:consolidate_protocols] do
@@ -1165,7 +1167,6 @@ defmodule Mix.Tasks.Release do
[]
end
release = maybe_add_config_reader_provider(config, release, version_path)
vm_args_path = Path.join(version_path, "vm.args")
cookie_path = Path.join(release.path, "releases/COOKIE")
start_erl_path = Path.join(release.path, "releases/start_erl.data")
@@ -1188,20 +1189,27 @@ defmodule Mix.Tasks.Release do
default_path = config[:config_path] |> Path.dirname() |> Path.join("runtime.exs")
deprecated_path = config[:config_path] |> Path.dirname() |> Path.join("releases.exs")
path =
{path, reboot?} =
cond do
path = opts[:runtime_config_path] ->
path
{path, false}
File.exists?(default_path) ->
default_path
if File.exists?(deprecated_path) do
IO.warn(
"both #{inspect(default_path)} and #{inspect(deprecated_path)} have been " <>
"found, but only #{inspect(default_path)} will be used"
)
end
{default_path, false}
File.exists?(deprecated_path) ->
# TODO: Warn from Elixir v1.13 onwards
deprecated_path
{deprecated_path, true}
true ->
nil
{nil, false}
end
cond do
@@ -1211,7 +1219,8 @@ defmodule Mix.Tasks.Release do
File.cp!(path, Path.join(version_path, "runtime.exs"))
init = {:system, "RELEASE_ROOT", "/releases/#{release.version}/runtime.exs"}
opts = [path: init, env: Mix.env(), target: Mix.target(), imports: :disabled]
update_in(release.config_providers, &[{Config.Reader, opts} | &1])
release = update_in(release.config_providers, &[{Config.Reader, opts} | &1])
update_in(release.options, &Keyword.put_new(&1, :reboot_system_after_config, reboot?))
release.config_providers == [] ->
skipping("runtime configuration (#{default_path} not found)")
@@ -1421,7 +1430,7 @@ defmodule Mix.Tasks.Release do
defp release_mode(release, env_var) do
# TODO: Remove otp_release check once we require Erlang/OTP 23+
otp_gte_23? = :erlang.system_info(:otp_release) >= '23'
reboot? = Keyword.get(release.options, :reboot_system_after_config, true)
reboot? = Keyword.get(release.options, :reboot_system_after_config, false)
if otp_gte_23? and reboot? and release.config_providers != [] do
"-elixir -config_provider_reboot_mode #{env_var}"
+6 -5
View File
@@ -542,12 +542,12 @@ defmodule Mix.ReleaseTest do
end
test "writes the given sys_config with config providers" do
release = release(config_providers: @providers)
release = release(config_providers: @providers, reboot_system_after_config: true)
assert make_sys_config(release, [kernel: [key: :value]], "/foo/bar/bat") == :ok
assert File.read!(@sys_config) =~ "%% RUNTIME_CONFIG=true"
{:ok, [config]} = :file.consult(@sys_config)
assert %Config.Provider{} = provider = config[:elixir][:config_provider_init]
refute provider.prune_after_boot
refute provider.prune_runtime_sys_config_after_boot
assert provider.extra_config == [kernel: [start_distribution: true]]
assert config[:kernel] == [key: :value, start_distribution: false]
end
@@ -556,6 +556,7 @@ defmodule Mix.ReleaseTest do
release =
release(
config_providers: @providers,
reboot_system_after_config: true,
start_distribution_during_config: true,
prune_runtime_sys_config_after_boot: true
)
@@ -564,8 +565,8 @@ defmodule Mix.ReleaseTest do
assert File.read!(@sys_config) =~ "%% RUNTIME_CONFIG=true"
{:ok, [config]} = :file.consult(@sys_config)
assert %Config.Provider{} = provider = config[:elixir][:config_provider_init]
assert provider.reboot_after_config
assert provider.prune_after_boot
assert provider.reboot_system_after_config
assert provider.prune_runtime_sys_config_after_boot
assert provider.extra_config == []
assert config[:kernel] == [key: :value]
end
@@ -576,7 +577,7 @@ defmodule Mix.ReleaseTest do
assert File.read!(@sys_config) =~ "%% RUNTIME_CONFIG=false"
{:ok, [config]} = :file.consult(@sys_config)
assert %Config.Provider{} = provider = config[:elixir][:config_provider_init]
refute provider.reboot_after_config
refute provider.reboot_system_after_config
assert provider.extra_config == []
assert config[:kernel] == [key: :value]
end
+2 -2
View File
@@ -354,9 +354,9 @@ defmodule Mix.Tasks.ReleaseTest do
end)
end
test "assembles a bootable release with runtime configuration" do
test "assembles a rebootable release with runtime configuration" do
in_fixture("release_test", fn ->
config = [releases: [runtime_config: []]]
config = [releases: [runtime_config: [reboot_system_after_config: true]]]
Mix.Project.in_project(:release_test, ".", config, fn _ ->
File.write!("config/runtime.exs", """