summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@plataformatec.com.br>2019-12-30 14:05:55 +0100
committerJosé Valim <jose.valim@plataformatec.com.br>2019-12-30 14:06:05 +0100
commit7f86ea33346feca490c5f4df9922dd475a73fc73 (patch)
tree243527ea8512ac933dd6e5991acf7a92a2acb947
parenta54e90147996f402777ef740b31b83e12257a218 (diff)
downloadelixir-7f86ea33346feca490c5f4df9922dd475a73fc73.tar.gz
Allow reboot to be disabled in releases, closes #9552
-rw-r--r--lib/elixir/lib/config/provider.ex45
-rw-r--r--lib/elixir/test/elixir/config/provider_test.exs43
-rw-r--r--lib/elixir/test/elixir/fixtures/configs/kernel.exs3
-rw-r--r--lib/mix/lib/mix/release.ex26
-rw-r--r--lib/mix/lib/mix/tasks/release.ex9
-rw-r--r--lib/mix/test/mix/release_test.exs12
6 files changed, 117 insertions, 21 deletions
diff --git a/lib/elixir/lib/config/provider.ex b/lib/elixir/lib/config/provider.ex
index 8f9433a3b..4c97976a4 100644
--- a/lib/elixir/lib/config/provider.ex
+++ b/lib/elixir/lib/config/provider.ex
@@ -105,7 +105,13 @@ defmodule Config.Provider do
@callback load(config, state) :: config
@doc false
- defstruct [:providers, :config_path, extra_config: [], prune_after_boot: false]
+ defstruct [
+ :providers,
+ :config_path,
+ extra_config: [],
+ prune_after_boot: false,
+ reboot_after_config: true
+ ]
@doc """
Validates a `t:config_path/0`.
@@ -163,13 +169,40 @@ defmodule Config.Provider do
{:ok, %Config.Provider{} = provider} ->
path = resolve_config_path!(provider.config_path)
validate_no_cyclic_boot!(path)
+ loaded_applications = :application.loaded_applications()
+ original_config = read_config!(path)
- read_config!(path)
- |> Config.__merge__([{app, [{key, booted_key(provider, path)}]} | provider.extra_config])
- |> run_providers(provider)
- |> write_config!(path)
+ config =
+ original_config
+ |> Config.__merge__(provider.extra_config)
+ |> run_providers(provider)
- restart_fun.()
+ if provider.reboot_after_config do
+ config
+ |> Config.__merge__([{app, [{key, booted_key(provider, path)}]}])
+ |> write_config!(path)
+
+ restart_fun.()
+ else
+ for {app, _, _} <- loaded_applications, config[app] != original_config[app] do
+ abort("""
+ Cannot configure #{inspect(app)} because :reboot_after_config has been set \
+ to false and #{inspect(app)} has already been loaded, meaning any further \
+ configuration won't have an effect.
+
+ The configuration for #{inspect(app)} before config providers was:
+
+ #{inspect(original_config[app])}
+
+ The configuration for #{inspect(app)} after config providers was:
+
+ #{inspect(config[app])}
+ """)
+ end
+
+ _ = Application.put_all_env(config, persistent: true)
+ :ok
+ end
{:ok, {:booted, path}} ->
File.rm(path)
diff --git a/lib/elixir/test/elixir/config/provider_test.exs b/lib/elixir/test/elixir/config/provider_test.exs
index 1b6a7ec71..72cda6f5a 100644
--- a/lib/elixir/test/elixir/config/provider_test.exs
+++ b/lib/elixir/test/elixir/config/provider_test.exs
@@ -9,21 +9,18 @@ defmodule Config.ProviderTest do
import ExUnit.CaptureIO
@tmp_path tmp_path("config_provider")
- @env_var "ELIXIR_CONFIG_PROVIDER_PATH"
+ @env_var "ELIXIR_CONFIG_PROVIDER_BOOTED"
@config_app :config_app
@sys_config Path.join(@tmp_path, "sys.config")
setup context do
- System.put_env(@env_var, @tmp_path)
-
File.rm_rf(@tmp_path)
File.mkdir_p!(@tmp_path)
- File.write!(@sys_config, :io_lib.format("~tw.~n", [context[:sys_config] || []]), [:utf8])
+ write_sys_config!(context[:sys_config] || [])
on_exit(fn ->
Application.delete_env(@config_app, :config_providers)
System.delete_env(@env_var)
- System.delete_env("ELIXIR_CONFIG_PROVIDER_BOOTED")
end)
end
@@ -65,8 +62,15 @@ defmodule Config.ProviderTest do
end
test "resolve!" do
- assert Provider.resolve_config_path!("/foo") == "/foo"
- assert Provider.resolve_config_path!({:system, @env_var, "/bar"}) == @tmp_path <> "/bar"
+ env_var = "ELIXIR_CONFIG_PROVIDER_PATH"
+
+ try do
+ System.put_env(env_var, @tmp_path)
+ assert Provider.resolve_config_path!("/foo") == "/foo"
+ assert Provider.resolve_config_path!({:system, env_var, "/bar"}) == @tmp_path <> "/bar"
+ after
+ System.delete_env(env_var)
+ end
end
end
@@ -116,6 +120,27 @@ defmodule Config.ProviderTest do
init_and_assert_boot()
end) =~ "Got infinite loop when running Config.Provider"
end
+
+ test "returns without rebooting" do
+ reader = {Config.Reader, fixture_path("configs/kernel.exs")}
+ init = Config.Provider.init([reader], @sys_config, reboot_after_config: false)
+ Application.put_env(@config_app, :config_providers, init)
+
+ assert capture_abort(fn ->
+ Provider.boot(@config_app, :config_providers, fn ->
+ raise "should not be called"
+ end)
+ end) =~ "Cannot configure :kernel because :reboot_after_config has been set to false"
+
+ # Make sure values before and after match
+ write_sys_config!(kernel: [elixir_reboot: true])
+ Application.put_env(@config_app, :config_providers, init)
+ System.delete_env(@env_var)
+
+ Provider.boot(@config_app, :config_providers, fn -> raise "should not be called" end)
+ assert Application.get_env(:kernel, :elixir_reboot) == true
+ assert Application.get_env(:elixir_reboot, :key) == :value
+ end
end
defp init(opts) do
@@ -145,4 +170,8 @@ defmodule Config.ProviderTest do
assert_raise ErlangError, fun
end)
end
+
+ defp write_sys_config!(data) do
+ File.write!(@sys_config, :io_lib.format("~tw.~n", [data]), [:utf8])
+ end
end
diff --git a/lib/elixir/test/elixir/fixtures/configs/kernel.exs b/lib/elixir/test/elixir/fixtures/configs/kernel.exs
new file mode 100644
index 000000000..5020372a5
--- /dev/null
+++ b/lib/elixir/test/elixir/fixtures/configs/kernel.exs
@@ -0,0 +1,3 @@
+import Config
+config :kernel, :elixir_reboot, true
+config :elixir_reboot, :key, :value
diff --git a/lib/mix/lib/mix/release.ex b/lib/mix/lib/mix/release.ex
index cb6e46c70..3966f137b 100644
--- a/lib/mix/lib/mix/release.ex
+++ b/lib/mix/lib/mix/release.ex
@@ -381,6 +381,7 @@ defmodule Mix.Release do
It uses the following release options to customize its behaviour:
+ * `:reboot_system_after_config`
* `:start_distribution_during_config`
* `:prune_runtime_sys_config_after_boot`
@@ -389,10 +390,12 @@ defmodule Mix.Release do
@spec make_sys_config(t, keyword(), Config.Provider.config_path()) ::
:ok | {:error, String.t()}
def make_sys_config(release, sys_config, config_provider_path) do
- {sys_config, runtime?} = merge_provider_config(release, sys_config, config_provider_path)
+ {sys_config, runtime_config?} =
+ merge_provider_config(release, sys_config, config_provider_path)
+
path = Path.join(release.version_path, "sys.config")
- args = [runtime?, sys_config]
+ args = [runtime_config?, sys_config]
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~tw.~n"
File.mkdir_p!(Path.dirname(path))
File.write!(path, :io_lib.format(format, args), [:utf8])
@@ -412,18 +415,27 @@ defmodule Mix.Release do
defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false}
defp merge_provider_config(release, sys_config, config_path) do
- {extra_config, initial_config} = start_distribution(release)
+ {reboot?, extra_config, initial_config} = start_distribution(release)
prune_after_boot = Keyword.get(release.options, :prune_runtime_sys_config_after_boot, false)
- opts = [extra_config: initial_config, prune_after_boot: prune_after_boot]
+
+ opts = [
+ extra_config: initial_config,
+ prune_after_boot: prune_after_boot,
+ reboot_after_config: reboot?
+ ]
+
init = Config.Provider.init(release.config_providers, config_path, opts)
{Config.Reader.merge(sys_config, [elixir: [config_providers: init]] ++ extra_config), true}
end
defp start_distribution(%{options: opts}) do
- if Keyword.get(opts, :start_distribution_during_config, false) do
- {[], []}
+ reboot? = Keyword.get(opts, :reboot_system_after_config, true)
+ early_distribution? = Keyword.get(opts, :start_distribution_during_config, false)
+
+ if not reboot? or early_distribution? do
+ {reboot?, [], []}
else
- {[kernel: [start_distribution: false]], [kernel: [start_distribution: true]]}
+ {true, [kernel: [start_distribution: false]], [kernel: [start_distribution: true]]}
end
end
diff --git a/lib/mix/lib/mix/tasks/release.ex b/lib/mix/lib/mix/tasks/release.ex
index 5b09416cb..087ac9a34 100644
--- a/lib/mix/lib/mix/tasks/release.ex
+++ b/lib/mix/lib/mix/tasks/release.ex
@@ -609,13 +609,20 @@ defmodule Mix.Tasks.Release do
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`.
+
* `: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 start`). If you need a restart,
+ `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`.
diff --git a/lib/mix/test/mix/release_test.exs b/lib/mix/test/mix/release_test.exs
index 9f43f5ad0..75e7af3a0 100644
--- a/lib/mix/test/mix/release_test.exs
+++ b/lib/mix/test/mix/release_test.exs
@@ -506,11 +506,23 @@ 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_providers]
+ assert provider.reboot_after_config
assert provider.prune_after_boot
assert provider.extra_config == []
assert config[:kernel] == [key: :value]
end
+ test "writes the given sys_config without reboot" do
+ release = release(config_providers: @providers, reboot_system_after_config: false)
+ 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_providers]
+ refute provider.reboot_after_config
+ assert provider.extra_config == []
+ assert config[:kernel] == [key: :value]
+ end
+
test "errors on bad config" do
assert {:error, "Could not read configuration file." <> _} =
make_sys_config(release([]), [foo: self()], "unused/runtime/path")